MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Angular服务的高效使用与管理

2023-07-176.8k 阅读

Angular 服务基础概念

在 Angular 应用开发中,服务是一个广义的概念,它本质上是一个单例对象,为整个应用或应用的特定部分提供特定功能。服务可以封装业务逻辑、处理数据获取与存储、管理应用状态等。从架构层面看,服务有助于将应用的不同功能模块进行分离,提高代码的可维护性和可测试性。

Angular 服务通过依赖注入(Dependency Injection,简称 DI)机制被其他组件或服务所使用。这意味着组件不需要自己去创建服务实例,而是由 Angular 框架负责创建并注入到需要它的地方。例如,假设我们有一个 UserService 用于处理用户相关的业务逻辑,在组件中使用它时,我们只需在组件的构造函数中声明依赖:

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

@Component({
  selector: 'app-my-component',
  templateUrl: './my-component.html',
  styleUrls: ['./my-component.css']
})
export class MyComponent {
  constructor(private userService: UserService) {}
}

在上述代码中,Angular 框架会创建 UserService 的实例并注入到 MyComponent 中。这里,UserService 就是一个典型的 Angular 服务。

创建 Angular 服务

创建 Angular 服务非常简单,使用 Angular CLI 可以快速生成服务的基本框架。在项目根目录下执行以下命令:

ng generate service my - service

这会在 src/app 目录下生成 my - service.ts 文件,内容大致如下:

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

@Injectable({
  providedIn: 'root'
})
export class MyService {
  constructor() {}
}

这里,@Injectable() 装饰器是必须的,它告诉 Angular 这个类是一个可注入的服务。providedIn: 'root' 表示该服务在应用的根模块中被提供,这是 Angular 6 及以上版本引入的一种便捷的服务提供方式。如果不使用这种方式,也可以在模块的 providers 数组中手动注册服务:

import { NgModule } from '@angular/core';
import { MyService } from './my - service';

@NgModule({
  providers: [MyService]
})
export class AppModule {}

服务中的业务逻辑封装

以一个简单的任务管理应用为例,我们创建一个 TaskService 来管理任务相关的操作。假设任务数据结构如下:

export interface Task {
  id: number;
  title: string;
  completed: boolean;
}

TaskService 可以提供方法来添加任务、获取任务列表、更新任务状态等。

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

@Injectable({
  providedIn: 'root'
})
export class TaskService {
  private tasks: Task[] = [];

  constructor() {}

  addTask(task: Task) {
    this.tasks.push(task);
  }

  getTasks(): Task[] {
    return this.tasks;
  }

  updateTaskStatus(taskId: number, completed: boolean) {
    const task = this.tasks.find(t => t.id === taskId);
    if (task) {
      task.completed = completed;
    }
  }
}

在组件中使用 TaskService

import { Component } from '@angular/core';
import { TaskService } from './task.service';
import { Task } from './task';

@Component({
  selector: 'app - task - component',
  templateUrl: './task - component.html',
  styleUrls: ['./task - component.css']
})
export class TaskComponent {
  newTask: Task = { id: 0, title: '', completed: false };

  constructor(private taskService: TaskService) {}

  addNewTask() {
    this.taskService.addTask(this.newTask);
    this.newTask = { id: 0, title: '', completed: false };
  }

  getTasks() {
    return this.taskService.getTasks();
  }

  updateTaskStatus(taskId: number, completed: boolean) {
    this.taskService.updateTaskStatus(taskId, completed);
  }
}

服务与数据获取

在实际应用中,服务常常用于与后端 API 进行数据交互。Angular 提供了 HttpClient 模块来处理 HTTP 请求。首先,在模块中导入 HttpClientModule

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { HttpClientModule } from '@angular/common/http';

import { AppComponent } from './app.component';

@NgModule({
  imports: [BrowserModule, HttpClientModule],
  declarations: [AppComponent],
  bootstrap: [AppComponent]
})
export class AppModule {}

然后创建一个 DataService 来获取数据:

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

@Injectable({
  providedIn: 'root'
})
export class DataService {
  private apiUrl = 'https://example.com/api/data';

  constructor(private http: HttpClient) {}

  getData(): Observable<any> {
    return this.http.get(this.apiUrl);
  }
}

在组件中使用 DataService 获取数据:

import { Component } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'app - data - component',
  templateUrl: './data - component.html',
  styleUrls: ['./data - component.css']
})
export class DataComponent {
  data: any;

  constructor(private dataService: DataService) {}

  ngOnInit() {
    this.dataService.getData().subscribe(result => {
      this.data = result;
    });
  }
}

服务的依赖注入原理

依赖注入是 Angular 服务管理的核心机制。Angular 使用注入器(Injector)来创建和管理服务实例。注入器维护一个依赖关系树,当一个组件请求某个服务时,注入器会查找该服务的实例。如果实例不存在,注入器会根据服务的定义创建一个新的实例。

例如,当 MyComponent 请求 UserService 时,注入器会在其内部查找 UserService 的实例。如果在根注入器中没有找到,并且 UserService 被配置为在根模块中提供,注入器会创建一个 UserService 的实例并返回。如果 UserService 依赖其他服务,比如 AuthService,注入器会首先确保 AuthService 的实例被创建并注入到 UserService 中。

在 Angular 中,有不同层次的注入器。根注入器为整个应用提供服务实例,组件注入器则为特定组件及其子组件提供服务实例。这意味着如果在组件中重新提供了某个服务,该组件及其子组件将使用组件注入器提供的服务实例,而不是根注入器的实例。

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

@Component({
  selector: 'app - my - component',
  templateUrl: './my - component.html',
  styleUrls: ['./my - component.css'],
  providers: [UserService]
})
export class MyComponent {
  constructor(private userService: UserService) {}
}

在上述代码中,MyComponent 及其子组件将使用组件注入器创建的 UserService 实例,与根注入器创建的实例相互隔离。

服务的单例特性

Angular 服务默认是单例的,这意味着在整个应用或特定注入器作用域内,只会有一个服务实例。例如,在根注入器提供的服务,在整个应用中只有一个实例。这对于共享数据和状态管理非常有用。

UserService 为例,如果它用于存储当前登录用户的信息,无论哪个组件使用 UserService,获取到的都是同一个用户信息实例。

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

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private currentUser: any;

  constructor() {}

  setCurrentUser(user: any) {
    this.currentUser = user;
  }

  getCurrentUser() {
    return this.currentUser;
  }
}

在不同组件中使用:

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

@Component({
  selector: 'app - component - a',
  templateUrl: './component - a.html',
  styleUrls: ['./component - a.css']
})
export class ComponentA {
  constructor(private userService: UserService) {
    const user = { name: 'John', age: 30 };
    this.userService.setCurrentUser(user);
  }
}

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

@Component({
  selector: 'app - component - b',
  templateUrl: './component - b.html',
  styleUrls: ['./component - b.css']
})
export class ComponentB {
  constructor(private userService: UserService) {
    const currentUser = this.userService.getCurrentUser();
    console.log(currentUser); // 输出 { name: 'John', age: 30 }
  }
}

服务的测试

为了确保服务的正确性和稳定性,对服务进行单元测试是非常必要的。Angular 提供了 TestBed 来帮助我们进行服务测试。

TaskService 为例,测试文件 task.service.spec.ts 内容如下:

import { TestBed } from '@angular/core/testing';
import { TaskService } from './task.service';
import { Task } from './task';

describe('TaskService', () => {
  let service: TaskService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [TaskService]
    });
    service = TestBed.inject(TaskService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('should add a task', () => {
    const task: Task = { id: 1, title: 'Test Task', completed: false };
    service.addTask(task);
    const tasks = service.getTasks();
    expect(tasks.length).toBe(1);
    expect(tasks[0]).toEqual(task);
  });

  it('should update task status', () => {
    const task: Task = { id: 1, title: 'Test Task', completed: false };
    service.addTask(task);
    service.updateTaskStatus(1, true);
    const tasks = service.getTasks();
    expect(tasks[0].completed).toBe(true);
  });
});

在上述测试中,beforeEach 钩子函数使用 TestBed.configureTestingModule 来配置测试模块,并提供 TaskService。然后通过 TestBed.inject 获取服务实例。每个 it 块定义了一个具体的测试用例,用于验证服务的不同功能。

服务的分层与模块化

随着应用规模的增大,合理的服务分层和模块化可以提高代码的可维护性和可扩展性。可以将服务分为不同层次,例如数据访问层服务用于与后端 API 交互,业务逻辑层服务处理具体的业务规则,公用工具服务提供通用的功能。

例如,在一个电商应用中,我们可以有 ProductDataService 负责从后端获取产品数据,CartService 处理购物车相关的业务逻辑,StringUtils 作为公用工具服务提供字符串处理功能。

// product - data.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class ProductDataService {
  private apiUrl = 'https://example.com/api/products';

  constructor(private http: HttpClient) {}

  getProducts(): Observable<any> {
    return this.http.get(this.apiUrl);
  }
}

// cart.service.ts
import { Injectable } from '@angular/core';
import { Product } from './product';

@Injectable({
  providedIn: 'root'
})
export class CartService {
  private cart: Product[] = [];

  constructor() {}

  addToCart(product: Product) {
    this.cart.push(product);
  }

  getCart(): Product[] {
    return this.cart;
  }
}

// string - utils.service.ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class StringUtils {
  capitalize(str: string) {
    return str.charAt(0).toUpperCase() + str.slice(1);
  }
}

在模块中,我们可以根据功能将相关服务分组。例如,数据访问层服务可以放在一个 DataAccessModule 中,业务逻辑层服务放在 BusinessLogicModule 中,公用工具服务放在 SharedModule 中。

// data - access.module.ts
import { NgModule } from '@angular/core';
import { ProductDataService } from './product - data.service';

@NgModule({
  providers: [ProductDataService]
})
export class DataAccessModule {}

// business - logic.module.ts
import { NgModule } from '@angular/core';
import { CartService } from './cart.service';

@NgModule({
  providers: [CartService]
})
export class BusinessLogicModule {}

// shared.module.ts
import { NgModule } from '@angular/core';
import { StringUtils } from './string - utils.service';

@NgModule({
  providers: [StringUtils]
})
export class SharedModule {}

然后在 AppModule 中导入这些模块:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { DataAccessModule } from './data - access.module';
import { BusinessLogicModule } from './business - logic.module';
import { SharedModule } from './shared.module';

import { AppComponent } from './app.component';

@NgModule({
  imports: [BrowserModule, DataAccessModule, BusinessLogicModule, SharedModule],
  declarations: [AppComponent],
  bootstrap: [AppComponent]
})
export class AppModule {}

服务的懒加载

在大型应用中,为了提高应用的加载性能,可以对部分服务进行懒加载。当需要使用这些服务时才进行加载,而不是在应用启动时就加载所有服务。

假设我们有一个 FeatureService 用于某个特定功能模块,我们可以将其配置为懒加载。首先,创建一个单独的模块 FeatureModule 来包含 FeatureService

// feature.module.ts
import { NgModule } from '@angular/core';
import { FeatureService } from './feature.service';

@NgModule({
  providers: [FeatureService]
})
export class FeatureModule {}

然后在路由配置中使用 loadChildren 来实现懒加载:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  {
    path: 'feature',
    loadChildren: () => import('./feature.module').then(m => m.FeatureModule)
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

这样,当用户访问 /feature 路由时,FeatureModule 及其包含的 FeatureService 才会被加载。

服务中的错误处理

在服务中,尤其是涉及数据获取和其他可能失败的操作时,错误处理是非常重要的。以 DataService 为例,当使用 HttpClient 进行 HTTP 请求时,可能会遇到网络错误、服务器错误等。

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

@Injectable({
  providedIn: 'root'
})
export class DataService {
  private apiUrl = 'https://example.com/api/data';

  constructor(private http: HttpClient) {}

  getData(): Observable<any> {
    return this.http.get(this.apiUrl).pipe(
      catchError(error => {
        console.error('Error fetching data:', error);
        // 可以在这里进行更复杂的错误处理,比如显示用户友好的错误信息
        throw error;
      })
    );
  }
}

在上述代码中,使用 catchError 操作符来捕获 HTTP 请求过程中的错误。在捕获到错误后,首先在控制台打印错误信息,然后重新抛出错误,这样调用该服务的组件可以根据需要进一步处理错误。

在组件中处理错误:

import { Component } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'app - data - component',
  templateUrl: './data - component.html',
  styleUrls: ['./data - component.css']
})
export class DataComponent {
  data: any;

  constructor(private dataService: DataService) {}

  ngOnInit() {
    this.dataService.getData().subscribe(
      result => {
        this.data = result;
      },
      error => {
        // 组件层面的错误处理,比如显示错误提示给用户
        console.error('Component caught error:', error);
      }
    );
  }
}

服务与 RxJS 的结合使用

RxJS(Reactive Extensions for JavaScript)是 Angular 中处理异步操作和事件流的强大工具。服务常常与 RxJS 结合使用,特别是在数据获取和状态管理方面。

例如,DataService 返回的 Observable 可以进行各种 RxJS 操作。假设我们需要对获取到的数据进行过滤和映射:

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

@Injectable({
  providedIn: 'root'
})
export class DataService {
  private apiUrl = 'https://example.com/api/data';

  constructor(private http: HttpClient) {}

  getData(): Observable<any> {
    return this.http.get(this.apiUrl).pipe(
      filter(data => data.some(item => item.active)),
      map(data => data.map(item => ({ id: item.id, name: item.name.toUpperCase() })))
    );
  }
}

在上述代码中,首先使用 filter 操作符过滤出 active 属性为 true 的数据项,然后使用 map 操作符将数据项映射为只包含 id 和大写 name 的新对象。

服务的优化与性能提升

  1. 减少不必要的实例创建:由于服务的单例特性,要确保在设计服务时,不会因为错误的配置导致多次创建相同功能的服务实例。例如,避免在组件层级重复提供应该在根层级提供的服务。
  2. 缓存数据:对于频繁获取且不经常变化的数据,服务可以进行缓存。以 DataService 为例,可以在服务内部缓存获取到的数据:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class DataService {
  private apiUrl = 'https://example.com/api/data';
  private cachedData: any;

  constructor(private http: HttpClient) {}

  getData(): Observable<any> {
    if (this.cachedData) {
      return new Observable(observer => {
        observer.next(this.cachedData);
        observer.complete();
      });
    }
    return this.http.get(this.apiUrl).pipe(
      (data => {
        this.cachedData = data;
        return data;
      })
    );
  }
}
  1. 优化 HTTP 请求:在进行数据获取时,合理设置 HTTP 请求的参数,如 cache - control 头,以利用浏览器缓存。并且尽量合并多个相关的 HTTP 请求,减少网络请求次数。

服务与 Angular 应用架构

服务在 Angular 应用架构中扮演着重要的角色。它们帮助实现了关注点分离,使得组件可以专注于展示和用户交互,而服务负责处理业务逻辑、数据访问等任务。

例如,在一个典型的 MVC(Model - View - Controller)或 MVVM(Model - View - ViewModel)架构风格的 Angular 应用中,服务可以看作是 “Model” 部分的一部分,负责管理数据和业务规则。组件作为 “View” 和 “ViewModel”,通过依赖注入使用服务提供的数据和功能。

这种架构设计使得应用更加易于维护和扩展。当业务需求发生变化时,只需要修改相应的服务,而不会影响到其他不相关的组件。同时,服务的单例特性和依赖注入机制也有助于实现应用的高内聚、低耦合。

总结

Angular 服务是构建高效、可维护的 Angular 应用的关键部分。通过合理地创建、使用、管理和测试服务,我们可以将应用的功能进行清晰的分离,提高代码的可复用性和可测试性。同时,结合 RxJS、HTTP 模块以及依赖注入机制,服务能够有效地处理各种业务场景,从简单的数据管理到复杂的异步操作和状态管理。在实际开发中,遵循良好的服务设计原则,如分层、模块化、懒加载等,可以进一步提升应用的性能和可扩展性,为用户提供更好的体验。无论是小型项目还是大型企业级应用,掌握 Angular 服务的高效使用与管理都是 Angular 开发者必备的技能。