跳到主要内容

服务与依赖注入

依赖注入(Dependency Injection,DI)是 Angular 的核心设计模式之一,它让代码更加模块化、可测试和可维护。本章将详细介绍如何创建服务以及 Angular 的依赖注入系统。

什么是依赖注入?

依赖注入是一种设计模式,类的依赖不由类自己创建,而是从外部注入。这样做的好处是:

  1. 解耦:类不需要知道依赖的具体实现
  2. 可测试:测试时可以轻松替换依赖
  3. 可维护:依赖的创建和管理集中处理
  4. 可复用:服务可以在多个组件间共享

没有 DI 的情况

// 不好的做法:类自己创建依赖
export class UserService {
private http = new HttpClient(); // 紧耦合,难以测试

getUsers() {
return this.http.get('/api/users');
}
}

使用 DI 的情况

// 好的做法:通过依赖注入获取依赖
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({ providedIn: 'root' })
export class UserService {
private http = inject(HttpClient); // 松耦合,易于测试

getUsers() {
return this.http.get('/api/users');
}
}

创建服务

使用 CLI 创建

ng generate service user
# 或简写
ng g s user

手动创建服务

import { Injectable } from '@angular/core';

@Injectable({
providedIn: 'root' // 在根注入器中提供,整个应用共享单例
})
export class UserService {
private users = [
{ id: 1, name: '张三', email: '[email protected]' },
{ id: 2, name: '李四', email: '[email protected]' }
];

getUsers() {
return this.users;
}

getUserById(id: number) {
return this.users.find(u => u.id === id);
}

addUser(user: { name: string; email: string }) {
const newUser = {
id: Math.max(...this.users.map(u => u.id)) + 1,
...user
};
this.users.push(newUser);
return newUser;
}
}

使用 inject() 函数

Angular 14+ 推荐使用 inject() 函数来注入依赖:

import { Component, inject } from '@angular/core';
import { UserService } from './user.service';
import { Router } from '@angular/router';

@Component({
selector: 'app-user-list',
template: `
<ul>
@for (user of users; track user.id) {
<li (click)="selectUser(user.id)">{{ user.name }}</li>
}
</ul>
`
})
export class UserListComponent {
// 使用 inject 函数注入服务
private userService = inject(UserService);
private router = inject(Router);

users = this.userService.getUsers();

selectUser(id: number) {
this.router.navigate(['/users', id]);
}
}

inject() 的使用位置

import { Injectable, inject, Component, Directive } from '@angular/core';

// 在服务中
@Injectable({ providedIn: 'root' })
export class AuthService {
private http = inject(HttpClient); // ✅ 类属性初始化
}

// 在组件中
@Component({ /* ... */ })
export class MyComponent {
private service = inject(MyService); // ✅ 类属性初始化

constructor() {
const another = inject(AnotherService); // ✅ 构造函数中
}
}

// 在指令中
@Directive({ /* ... */ })
export class MyDirective {
private element = inject(ElementRef); // ✅ 类属性初始化
}

// 在函数中(需要在注入上下文中)
export const authGuard = () => {
const auth = inject(AuthService); // ✅ 路由守卫
return auth.isLoggedIn();
};

提供者(Providers)

提供者告诉 Angular 如何创建依赖的实例。

providedIn 配置

import { Injectable } from '@angular/core';

// 根注入器 - 整个应用共享单例
@Injectable({ providedIn: 'root' })
export class GlobalService {}

// 平台注入器 - 多个应用共享
@Injectable({ providedIn: 'platform' })
export class PlatformService {}

// 任何注入器 - 每个注入器创建新实例
@Injectable({ providedIn: 'any' })
export class AnyService {}

// 不提供 - 需要手动在模块或组件中提供
@Injectable()
export class ManualService {}

在组件级别提供

import { Component } from '@angular/core';
import { UserService } from './user.service';

@Component({
selector: 'app-user-profile',
template: `...`,
providers: [
UserService, // 该组件及其子组件共享此实例
]
})
export class UserProfileComponent {}

在组件级别提供(使用工厂)

import { Component } from '@angular/core';
import { UserService } from './user.service';
import { MockUserService } from './mock-user.service';

@Component({
selector: 'app-user-profile',
template: `...`,
providers: [
{
provide: UserService,
useClass: MockUserService // 使用 Mock 实现
}
]
})
export class UserProfileComponent {}

注入器层级

Angular 应用有一个注入器树,与组件树平行。子注入器可以覆盖父注入器的提供者。

根注入器 (root)
├── AppModule providers
├── AppComponent
│ ├── 子组件 A (有自己的 providers)
│ │ └── 孙组件 A1
│ └── 子组件 B
└── ...

注入器查找顺序

当请求一个依赖时,Angular 会:

  1. 在当前组件/指令的注入器中查找
  2. 如果没找到,向父注入器查找
  3. 一直查找到根注入器
  4. 如果都没找到,抛出错误
// 父组件提供 UserService
@Component({
selector: 'app-parent',
template: `<app-child></app-child>`,
providers: [UserService]
})
export class ParentComponent {}

// 子组件使用父组件的 UserService
@Component({
selector: 'app-child',
template: `...`
// 没有自己的 providers,使用父组件的 UserService
})
export class ChildComponent {
userService = inject(UserService); // 来自父组件
}

// 孙组件覆盖 UserService
@Component({
selector: 'app-grandchild',
template: `...`,
providers: [
{ provide: UserService, useClass: SpecialUserService }
]
})
export class GrandchildComponent {
userService = inject(UserService); // 使用自己的 SpecialUserService
}

服务示例:状态管理

import { Injectable, signal, computed } from '@angular/core';

export interface CartItem {
id: number;
name: string;
price: number;
quantity: number;
}

@Injectable({ providedIn: 'root' })
export class CartService {
// 私有可写信号
private items = signal<CartItem[]>([]);

// 公开只读信号
readonly cartItems = this.items.asReadonly();

// 计算信号
readonly totalItems = computed(() =>
this.items().reduce((sum, item) => sum + item.quantity, 0)
);

readonly totalPrice = computed(() =>
this.items().reduce((sum, item) => sum + item.price * item.quantity, 0)
);

addItem(item: Omit<CartItem, 'quantity'>) {
this.items.update(items => {
const existing = items.find(i => i.id === item.id);
if (existing) {
return items.map(i =>
i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
);
}
return [...items, { ...item, quantity: 1 }];
});
}

removeItem(id: number) {
this.items.update(items => items.filter(i => i.id !== id));
}

updateQuantity(id: number, quantity: number) {
if (quantity <= 0) {
this.removeItem(id);
return;
}
this.items.update(items =>
items.map(i => i.id === id ? { ...i, quantity } : i)
);
}

clearCart() {
this.items.set([]);
}
}

在组件中使用:

import { Component, inject } from '@angular/core';
import { CartService } from './cart.service';

@Component({
selector: 'app-cart',
template: `
<h2>购物车</h2>

@for (item of cartService.cartItems(); track item.id) {
<div class="cart-item">
<span>{{ item.name }}</span>
<span>¥{{ item.price }}</span>
<button (click)="updateQuantity(item.id, item.quantity - 1)">-</button>
<span>{{ item.quantity }}</span>
<button (click)="updateQuantity(item.id, item.quantity + 1)">+</button>
<button (click)="removeItem(item.id)">删除</button>
</div>
}

<div class="summary">
<p>共 {{ cartService.totalItems() }} 件商品</p>
<p>总计: ¥{{ cartService.totalPrice() }}</p>
<button (click)="clearCart()">清空购物车</button>
</div>
`
})
export class CartComponent {
cartService = inject(CartService);

updateQuantity(id: number, quantity: number) {
this.cartService.updateQuantity(id, quantity);
}

removeItem(id: number) {
this.cartService.removeItem(id);
}

clearCart() {
this.cartService.clearCart();
}
}

多重注入

当需要注入同一类型的多个实例时,使用 InjectionToken

import { Injectable, InjectionToken, inject } from '@angular/core';

// 定义接口
export interface LogHandler {
log(message: string): void;
}

// 定义 InjectionToken
export const LOG_HANDLERS = new InjectionToken<LogHandler[]>('LogHandlers');

// 实现多个处理器
@Injectable({ providedIn: 'root' })
export class ConsoleLogHandler implements LogHandler {
log(message: string) {
console.log('Console:', message);
}
}

@Injectable({ providedIn: 'root' })
export class FileLogHandler implements LogHandler {
log(message: string) {
// 写入文件
console.log('File:', message);
}
}

// 在组件或模块中提供多个
@Component({
selector: 'app-logger',
template: `...`,
providers: [
{ provide: LOG_HANDLERS, useClass: ConsoleLogHandler, multi: true },
{ provide: LOG_HANDLERS, useClass: FileLogHandler, multi: true },
]
})
export class LoggerComponent {
handlers = inject(LOG_HANDLERS);

log(message: string) {
this.handlers.forEach(handler => handler.log(message));
}
}

可选注入

使用 @Optional()inject() 的选项参数处理可选依赖:

import { Component, inject, Optional } from '@angular/core';

// 方式一:使用 inject 的选项
@Component({ /* ... */ })
export class MyComponent {
private logger = inject(LoggerService, { optional: true });

doSomething() {
this.logger?.log('Doing something');
}
}

// 方式二:使用 @Optional 装饰器(构造函数注入)
@Component({ /* ... */ })
export class MyComponent {
constructor(@Optional() private logger: LoggerService) {}
}

小结

  1. 依赖注入是 Angular 的核心模式,实现解耦和可测试性
  2. @Injectable 装饰器标记服务类,providedIn 配置提供范围
  3. inject() 函数是推荐的注入方式,简洁直观
  4. 提供者配置依赖的创建方式,支持不同层级的覆盖
  5. 注入器树决定了依赖的查找顺序

下一步

掌握了服务和依赖注入后,接下来学习 指令系统,了解如何扩展 HTML 元素的行为。