Chapter 03

依赖注入

Angular 最核心的架构特性:DI 系统让代码解耦、可测试、可复用

1. 什么是依赖注入

依赖注入(Dependency Injection,DI) 是一种设计模式:组件或服务不自己创建依赖对象,而是由外部(注入器)提供。Angular 的 DI 系统是框架的核心支柱,贯穿整个应用架构。

没有 DI(紧耦合)

class UserComponent {
  private service =
    new UserService(); // 自己 new
  // 问题:无法替换实现,难以测试
}

有 DI(松耦合)

class UserComponent {
  constructor(
    private service: UserService
  ) {} // Angular 自动提供
}

2. 注入器树(Injector Tree)

Angular 的注入器形成一棵树,与组件树平行。服务注册在哪一层,决定了它的作用域实例数量

3. @Injectable 装饰器

创建一个可注入服务,需要用 @Injectable() 装饰,并在 providedIn 中声明注册位置:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

export interface User {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user';
}

@Injectable({
  providedIn: 'root'  // 注册到根注入器(应用单例)
})
export class UserService {
  private baseUrl = '/api/users';

  constructor(private http: HttpClient) {}

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>(this.baseUrl);
  }

  getUser(id: number): Observable<User> {
    return this.http.get<User>(`${this.baseUrl}/${id}`);
  }

  createUser(data: Partial<User>): Observable<User> {
    return this.http.post<User>(this.baseUrl, data);
  }

  updateUser(id: number, data: Partial<User>): Observable<User> {
    return this.http.put<User>(`${this.baseUrl}/${id}`, data);
  }

  deleteUser(id: number): Observable<void> {
    return this.http.delete<void>(`${this.baseUrl}/${id}`);
  }
}

4. providedIn 策略详解

说明适用场景
'root'注册到应用根注入器,全局单例大多数服务(HTTP、认证、配置)
'platform'注册到平台级别,跨 Angular 应用共享多个 Angular 应用共存时
'any'每个懒加载模块/组件各自拥有一个实例需要模块级别隔离的服务
组件 providers每个组件实例有独立的服务实例有状态的组件级服务

5. inject() 函数(Angular 14+)

inject() 是 Angular 14 引入的函数式 API,可以在注入上下文中(构造函数、字段初始化器、工厂函数)直接注入依赖,无需通过构造函数参数。在 Standalone 和 Signals 时代,这是更现代的写法。

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

@Component({
  selector: 'app-user-list',
  standalone: true,
  template: `...`
})
export class UserListComponent implements OnInit {
  // 用 inject() 直接在字段初始化时注入(更简洁)
  private userService = inject(UserService);
  private router = inject(Router);
  private auth = inject(AuthService);

  users: User[] = [];

  ngOnInit() {
    this.userService.getUsers().subscribe(users => {
      this.users = users;
    });
  }
}

inject() 的优势

// 可复用的"Hook"函数(类比 React 自定义 Hook)
function useCurrentUser() {
  const auth = inject(AuthService);
  const router = inject(Router);

  return {
    user: auth.currentUser,
    logout: () => {
      auth.logout();
      router.navigate(['/login']);
    }
  };
}

@Component({ /* ... */ })
export class HeaderComponent {
  protected currentUser = useCurrentUser();
  // 直接在模板中用 currentUser.user 和 currentUser.logout()
}

6. 组件级别的服务(分层注入)

有时你需要每个组件实例拥有独立的服务实例(如购物车状态)。在 @Component.providers 中注册服务即可实现:

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

@Injectable()  // 注意:不加 providedIn
export class CartService {
  private items = signal<CartItem[]>([]);
  count = computed(() => this.items().length);
  total = computed(() =>
    this.items().reduce((sum, i) => sum + i.price * i.qty, 0)
  );

  addItem(item: CartItem) {
    this.items.update(items => [...items, item]);
  }
}

// 在组件中提供,每个组件实例获得独立的 CartService
@Component({
  selector: 'app-checkout',
  standalone: true,
  providers: [CartService],  // 组件级提供
  template: `...`
})
export class CheckoutComponent {
  protected cart = inject(CartService);
}

7. 完整实战:UserService + 认证服务

// auth.service.ts — 认证服务(根单例)
@Injectable({ providedIn: 'root' })
export class AuthService {
  private currentUser = signal<User | null>(null);
  private http = inject(HttpClient);
  private router = inject(Router);

  isLoggedIn = computed(() => this.currentUser() !== null);
  user = this.currentUser.asReadonly();

  login(email: string, password: string): Observable<User> {
    return this.http.post<User>('/api/auth/login', { email, password }).pipe(
      tap(user => {
        this.currentUser.set(user);
        localStorage.setItem('token', user.token);
      })
    );
  }

  logout(): void {
    this.currentUser.set(null);
    localStorage.removeItem('token');
    this.router.navigate(['/login']);
  }
}

本章小结:依赖注入是 Angular 解耦架构的核心。providedIn: 'root' 创建全局单例,组件级 providers 创建局部实例,inject() 函数是现代 Angular 推荐的注入方式。下一章学习路由系统。