《Angular 组件之间的交互的示例代码》
在 Angular 框架中,组件是构建用户界面的核心单元。不同组件之间往往需要共享数据、触发事件或协调行为,这种交互能力直接决定了应用的灵活性和可维护性。本文将通过具体示例,详细介绍 Angular 组件间交互的五种常见方式:输入属性(@Input)、输出属性(@Output)、服务(Service)、模板引用变量和 ViewChild,并提供完整的可运行代码。
一、输入属性(@Input)——父传子
输入属性允许父组件向子组件传递数据,通过 @Input 装饰器标记的属性接收父组件的值。
1.1 基本用法
父组件通过属性绑定传递数据,子组件使用 @Input 接收。
// 子组件 child.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-child',
template: `接收到的消息:{{ message }}
`
})
export class ChildComponent {
@Input() message: string = '';
}
// 父组件 parent.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-parent',
template: `
`
})
export class ParentComponent {
parentMessage = '初始消息';
changeMessage() {
this.parentMessage = '修改后的消息';
}
}
当父组件的 parentMessage 变化时,子组件的 message 会自动更新。
1.2 输入属性变化检测
可以使用 ngOnChanges 生命周期钩子监听输入属性变化:
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-child',
template: `当前消息:{{ message }}
`
})
export class ChildComponent implements OnChanges {
@Input() message: string = '';
ngOnChanges(changes: SimpleChanges) {
console.log('消息变化:', changes.message.currentValue);
}
}
二、输出属性(@Output)——子传父
输出属性允许子组件向父组件发送事件,通过 @Output 和 EventEmitter 实现。
2.1 基本用法
子组件定义事件,父组件通过事件绑定监听。
// 子组件 child.component.ts
import { Component, EventEmitter, Output } from '@angular/core';
@Component({
selector: 'app-child',
template: ``
})
export class ChildComponent {
@Output() messageEvent = new EventEmitter();
sendMessage() {
this.messageEvent.emit('来自子组件的消息');
}
}
// 父组件 parent.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-parent',
template: `
接收到的消息:{{ receivedMessage }}
`
})
export class ParentComponent {
receivedMessage = '';
receiveMessage(message: string) {
this.receivedMessage = message;
}
}
2.2 自定义事件对象
可以传递复杂对象作为事件数据:
// 子组件
@Output() customEvent = new EventEmitter();
sendCustomEvent() {
this.customEvent.emit({id: 1, text: '自定义事件'});
}
// 父组件
handleCustomEvent(event: {id: number, text: string}) {
console.log(event.id, event.text);
}
三、服务(Service)——跨组件通信
对于非父子关系的组件或需要共享的状态,使用依赖注入的服务是最佳选择。
3.1 创建可共享的服务
// data.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root' // 单例服务
})
export class DataService {
private messageSource = new BehaviorSubject('默认消息');
currentMessage = this.messageSource.asObservable();
changeMessage(message: string) {
this.messageSource.next(message);
}
}
3.2 组件间通过服务通信
// 组件A
import { Component } from '@angular/core';
import { DataService } from './data.service';
@Component({
selector: 'app-component-a',
template: `
`
})
export class ComponentA {
newMessage = '';
constructor(private dataService: DataService) {}
sendMessage() {
this.dataService.changeMessage(this.newMessage);
this.newMessage = '';
}
}
// 组件B
import { Component, OnInit } from '@angular/core';
import { DataService } from './data.service';
@Component({
selector: 'app-component-b',
template: `接收到的消息:{{ message }}
`
})
export class ComponentB implements OnInit {
message = '';
constructor(private dataService: DataService) {}
ngOnInit() {
this.dataService.currentMessage.subscribe(message => {
this.message = message;
});
}
}
四、模板引用变量——直接访问
模板引用变量允许在模板中直接访问子组件或DOM元素。
4.1 访问子组件方法
// 子组件
import { Component } from '@angular/core';
@Component({
selector: 'app-child',
template: `子组件内容
`
})
export class ChildComponent {
greet() {
alert('子组件问候');
}
}
// 父组件
import { Component, ViewChild, ElementRef } from '@angular/core';
@Component({
selector: 'app-parent',
template: `
`
})
export class ParentComponent {}
4.2 访问DOM元素
@Component({
selector: 'app-example',
template: `
`
})
export class ExampleComponent {
@ViewChild('myInput') myInput: ElementRef;
focusInput() {
this.myInput.nativeElement.focus();
}
}
五、ViewChild——程序化访问
当需要在组件类中访问子组件或DOM元素时,可以使用 ViewChild 装饰器。
5.1 基本用法
// 子组件
import { Component } from '@angular/core';
@Component({
selector: 'app-child',
template: `计数器:{{ count }}
`
})
export class ChildComponent {
count = 0;
increment() {
this.count++;
}
}
// 父组件
import { Component, ViewChild, AfterViewInit } from '@angular/core';
@Component({
selector: 'app-parent',
template: `
`
})
export class ParentComponent implements AfterViewInit {
@ViewChild('child') childComponent: ChildComponent;
ngAfterViewInit() {
console.log('子组件已加载', this.childComponent);
}
incrementFromParent() {
this.childComponent.increment();
}
}
5.2 访问多个子组件
使用 ViewChildren 获取多个子组件引用:
import { Component, ViewChildren, QueryList, AfterViewInit } from '@angular/core';
import { ChildComponent } from './child.component';
@Component({
selector: 'app-parent',
template: `
`
})
export class ParentComponent implements AfterViewInit {
@ViewChildren('child') childComponents: QueryList;
items = [1, 2, 3];
ngAfterViewInit() {
console.log('所有子组件', this.childComponents.toArray());
}
logAllCounts() {
this.childComponents.forEach((child, index) => {
console.log(`子组件${index + 1}计数: ${child.count}`);
});
}
}
六、综合示例:购物车组件
下面是一个完整的购物车示例,结合了多种组件交互方式:
// 产品服务
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
interface Product {
id: number;
name: string;
price: number;
}
@Injectable({
providedIn: 'root'
})
export class ProductService {
private products: Product[] = [
{id: 1, name: '手机', price: 2999},
{id: 2, name: '笔记本', price: 5999},
{id: 3, name: '耳机', price: 399}
];
private cartSource = new BehaviorSubject([]);
cartItems = this.cartSource.asObservable();
getProducts() {
return this.products;
}
addToCart(product: Product) {
const currentCart = this.cartSource.value;
const updatedCart = [...currentCart, product];
this.cartSource.next(updatedCart);
}
removeFromCart(productId: number) {
const currentCart = this.cartSource.value;
const updatedCart = currentCart.filter(item => item.id !== productId);
this.cartSource.next(updatedCart);
}
}
// 产品列表组件
import { Component } from '@angular/core';
import { ProductService } from './product.service';
import { Product } from './product.service';
@Component({
selector: 'app-product-list',
template: `
{{ product.name }}
价格: ¥{{ product.price }}
`
})
export class ProductListComponent {
products: Product[] = [];
constructor(private productService: ProductService) {
this.products = this.productService.getProducts();
}
addToCart(product: Product) {
this.productService.addToCart(product);
}
}
// 购物车组件
import { Component, OnInit } from '@angular/core';
import { ProductService } from './product.service';
import { Product } from './product.service';
@Component({
selector: 'app-cart',
template: `
购物车
购物车为空
{{ item.name }} - ¥{{ item.price }}
0">总价: ¥{{ totalPrice }}
`
})
export class CartComponent implements OnInit {
cartItems: Product[] = [];
totalPrice = 0;
constructor(private productService: ProductService) {}
ngOnInit() {
this.productService.cartItems.subscribe(items => {
this.cartItems = items;
this.calculateTotal();
});
}
removeFromCart(productId: number) {
this.productService.removeFromCart(productId);
}
calculateTotal() {
this.totalPrice = this.cartItems.reduce(
(sum, item) => sum + item.price, 0
);
}
}
// 根组件
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
在线商店
`
})
export class AppComponent {}
七、最佳实践建议
1. 简单父子通信优先使用 @Input 和 @Output
2. 复杂状态管理或跨组件通信使用服务
3. 避免过度使用 ViewChild,优先考虑响应式方式
4. 对于表单等复杂交互,考虑使用 ControlValueAccessor
5. 保持组件职责单一,通过组合而非继承实现复用
八、常见问题解决方案
问题1:@Input 属性未更新
解决方案:确保父组件属性变化,或使用 OnChanges 钩子手动处理
问题2:服务数据不同步
解决方案:使用 BehaviorSubject 确保初始值,并正确实现订阅管理
问题3:ViewChild 获取为 undefined
解决方案:确保在 ngAfterViewInit 生命周期钩子中访问
问题4:模板引用变量作用域
解决方案:明确变量作用范围,避免命名冲突
关键词
Angular组件交互、@Input、@Output、服务通信、模板引用变量、ViewChild、依赖注入、事件发射、BehaviorSubject、跨组件通信
简介
本文详细介绍了Angular框架中组件间交互的五种主要方式:通过@Input装饰器实现父传子、使用@Output和EventEmitter实现子传父、利用服务进行跨组件通信、通过模板引用变量直接访问子组件、以及使用ViewChild进行程序化访问。每种方式都提供了完整的代码示例,包括基础用法和进阶技巧,最后通过一个购物车综合示例展示了多种交互方式的结合使用。文章还包含了最佳实践建议和常见问题解决方案,帮助开发者构建高效、可维护的Angular应用。