Angular组件基础与创建

本章目标:深入理解Angular组件的核心概念,掌握组件的创建、使用和通信方式,构建可复用的UI组件。

什么是Angular组件?

组件是Angular应用的基本构建块,负责控制屏幕的一部分(视图)。每个组件包含:

TypeScript类

包含组件的业务逻辑和数据

HTML模板

定义组件的视图结构

样式文件

定义组件的外观样式

装饰器

提供组件的元数据

组件树结构示例
AppComponent (根组件)
HeaderComponent
MainContentComponent
FooterComponent
UserListComponent
ProductComponent

创建组件的两种方式

方式1:使用Angular CLI(推荐)

使用Angular CLI可以快速生成标准化的组件文件:

CLI命令
# 完整命令
ng generate component user-profile

# 简写
ng g c user-profile

# 带路径
ng g c admin/user-list

# 内联模板(不生成HTML文件)
ng g c login --inline-template

# 内联样式(不生成CSS文件)
ng g c login --inline-style

# 不生成测试文件
ng g c user --skip-tests
生成的文件结构
src/app/user-profile/
├── user-profile.component.ts
├── user-profile.component.html
├── user-profile.component.css
└── user-profile.component.spec.ts

方式2:手动创建组件

了解组件的每个部分,手动创建可以帮助深入理解:

user-profile.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-user-profile',
  templateUrl: './user-profile.component.html',
  styleUrls: ['./user-profile.component.css']
})
export class UserProfileComponent {
  // 组件属性
  userName = '张三';
  userAge = 25;
  isActive = true;

  // 组件方法
  getGreeting(): string {
    return `你好,${this.userName}!`;
  }

  toggleStatus(): void {
    this.isActive = !this.isActive;
  }
}
user-profile.component.html
<div class="user-card">
  <h2>{{ userName }}的档案</h2>
  <p>{{ getGreeting() }}</p>

  <div class="user-info">
    <p>年龄: {{ userAge }}</p>
    <p>状态:
      <span [class.active]="isActive"
            [class.inactive]="!isActive">
        {{ isActive ? '活跃' : '不活跃' }}
      </span>
    </p>
  </div>

  <button (click)="toggleStatus()">
    切换状态
  </button>
</div>

组件装饰器详解

@Component装饰器为组件提供元数据配置:

tr>
元数据属性 描述 示例
selector 定义组件在HTML中的标签名 selector: 'app-user'
templateUrl 指定外部模板文件路径 templateUrl: './user.component.html'
template 内联模板(直接写在TS文件中) template: <h1>{{title}}</h1>
styleUrls 外部样式文件路径(数组) styleUrls: ['./user.component.css']
styles 内联样式(直接写在TS文件中) styles: ['h1 { color: red; }']
changeDetection 变更检测策略 changeDetection: ChangeDetectionStrategy.OnPush

组件数据绑定

Angular提供了多种数据绑定方式:

数据绑定示例
属性绑定
<!-- 单向:数据源 -> 视图 -->
<img [src]="imageUrl">
<div [class.active]="isActive"></div>
事件绑定
<!-- 单向:视图 -> 数据源 -->
<button (click)="save()">保存</button>
<input (input)="onInput($event)">
双向绑定
<!-- 双向:视图 <-> 数据源 -->
<input [(ngModel)]="userName">
<app-child [(value)]="parentValue"></app-child>

组件生命周期钩子

Angular组件有一系列生命周期钩子,允许我们在特定阶段执行代码:

ngOnChanges

当输入属性发生变化时调用

变更检测
ngOnInit

组件初始化时调用(只执行一次)

创建阶段
ngDoCheck

每个变更检测周期调用

变更检测
ngAfterContentInit

内容投影初始化后调用

创建阶段
ngAfterContentChecked

每次检查内容投影后调用

变更检测
ngAfterViewInit

组件视图初始化后调用

创建阶段
ngAfterViewChecked

每次检查组件视图后调用

变更检测
ngOnDestroy

组件销毁前调用

销毁阶段
实现生命周期钩子
import { Component, OnInit, OnDestroy } from '@angular/core';

@Component({
  selector: 'app-lifecycle-demo',
  template: `<h1>生命周期演示</h1>`
})
export class LifecycleDemoComponent
  implements OnInit, OnDestroy {

  ngOnInit(): void {
    console.log('组件初始化完成');
    // 在这里执行初始化逻辑
    // 例如:加载数据、订阅事件
  }

  ngOnDestroy(): void {
    console.log('组件即将销毁');
    // 在这里执行清理逻辑
    // 例如:取消订阅、清理定时器
  }
}
最佳实践建议
  • ngOnInit:用于初始化数据,避免在构造函数中执行复杂逻辑
  • ngOnDestroy:必须清理订阅和定时器,防止内存泄漏
  • ngOnChanges:监控输入属性的变化,执行相应操作
  • ngAfterViewInit:访问DOM元素或子组件
  • 避免在ngDoCheck中执行复杂操作,会影响性能

组件通信

组件之间的通信是Angular应用的关键部分:

父组件

通过属性传递数据

@Input()
子组件

通过事件发送数据

@Output()
父组件 → 子组件(@Input)
// parent.component.ts
@Component({
  template: `
    <app-child
      [userData]="currentUser"
      [showDetails]="true">
    </app-child>
  `
})
export class ParentComponent {
  currentUser = { name: '张三', age: 25 };
}

// child.component.ts
@Component({...})
export class ChildComponent {
  @Input() userData: any;
  @Input() showDetails: boolean = false;
}
子组件 → 父组件(@Output)
// child.component.ts
@Component({...})
export class ChildComponent {
  @Output() userUpdated = new EventEmitter<any>();

  updateUser(): void {
    this.userUpdated.emit({
      name: '李四',
      age: 30
    });
  }
}

// parent.component.ts
@Component({
  template: `
    <app-child
      (userUpdated)="onUserUpdated($event)">
    </app-child>
  `
})
export class ParentComponent {
  onUserUpdated(user: any): void {
    console.log('用户已更新:', user);
  }
}

通过服务进行组件通信

对于非父子关系的组件,可以使用服务进行通信:

// data.service.ts - 共享数据服务
@Injectable({ providedIn: 'root' })
export class DataService {
  private dataSubject = new BehaviorSubject<string>('');
  data$ = this.dataSubject.asObservable();

  updateData(newData: string): void {
    this.dataSubject.next(newData);
  }
}

// component-a.ts - 发送数据
@Component({...})
export class ComponentA {
  constructor(private dataService: DataService) {}

  sendData(): void {
    this.dataService.updateData('Hello from A');
  }
}

// component-b.ts - 接收数据
@Component({...})
export class ComponentB {
  receivedData: string = '';

  constructor(private dataService: DataService) {
    this.dataService.data$.subscribe(data => {
      this.receivedData = data;
    });
  }
}

组件最佳实践

组件设计原则
  • 单一职责:每个组件只做一件事
  • 可复用性:设计通用的、可配置的组件
  • 松耦合:减少组件间的直接依赖
  • 高内聚:相关功能放在同一个组件
  • 容器组件:管理数据逻辑
  • 展示组件:只负责UI展示
常见错误
  • 在构造函数中执行复杂逻辑
  • 忘记在ngOnDestroy中清理订阅
  • 在模板中使用复杂的表达式
  • 组件过大,职责过多
  • 过度使用双向绑定
  • 在组件中直接操作DOM

完整示例:用户卡片组件

创建一个完整的、可复用的用户卡片组件:

// user-card.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-user-card',
  templateUrl: './user-card.component.html',
  styleUrls: ['./user-card.component.css']
})
export class UserCardComponent {
  @Input() user: User = {
    id: 0,
    name: '',
    email: '',
    avatar: '',
    isActive: false
  };

  @Input() showActions: boolean = true;

  @Output() selected = new EventEmitter<number>();
  @Output() deleted = new EventEmitter<number>();

  onSelect(): void {
    this.selected.emit(this.user.id);
  }

  onDelete(): void {
    this.deleted.emit(this.user.id);
  }

  getStatusText(): string {
    return this.user.isActive ? '活跃' : '离线';
  }

  getStatusClass(): string {
    return this.user.isActive ? 'status-active' : 'status-inactive';
  }
}

interface User {
  id: number;
  name: string;
  email: string;
  avatar: string;
  isActive: boolean;
}
<!-- user-card.component.html -->
<div class="user-card" [class.card-active]="user.isActive">
  <div class="card-header">
    <img [src]="user.avatar" [alt]="user.name" class="avatar">
    <div class="user-info">
      <h3>{{ user.name }}</h3>
      <p class="email">{{ user.email }}</p>
    </div>
    <span class="status" [class]="getStatusClass()">
      {{ getStatusText() }}
    </span>
  </div>

  <div class="card-body">
    <p>用户ID: {{ user.id }}</p>
  </div>

  <div class="card-footer" *ngIf="showActions">
    <button class="btn btn-primary" (click)="onSelect()">
      选择用户
    </button>
    <button class="btn btn-danger" (click)="onDelete()">
      删除
    </button>
  </div>
</div>

本章总结

通过本章学习,你应该掌握了:

  • Angular组件的结构和创建方式
  • 组件装饰器的配置和元数据
  • 数据绑定的三种方式
  • 完整的组件生命周期钩子
  • 父子组件之间的通信机制
  • 组件设计的最佳实践