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

Angular服务的构建与调用方法

2021-07-022.1k 阅读

1. Angular服务简介

在Angular应用程序中,服务是一个广义的概念,它本质上是一个类,用于处理特定的功能或业务逻辑。这些功能可以包括数据获取、数据处理、共享数据管理、与后端服务器的通信等。通过将这些功能封装在服务中,Angular应用程序的代码结构变得更加清晰,实现了模块化和可维护性。

例如,假设有一个电子商务应用程序,其中需要从服务器获取产品列表数据。我们可以创建一个ProductService来处理与获取产品数据相关的所有操作,这样在组件中只需要调用这个服务的方法,而不需要关心具体的数据获取逻辑。这不仅使得组件的代码更简洁,也方便在多个组件之间复用数据获取的逻辑。

2. 构建Angular服务

2.1 使用Angular CLI创建服务

Angular CLI(命令行界面)是Angular开发中非常强大的工具,它提供了快速创建各种Angular元素的命令,包括服务。使用以下命令可以创建一个新的服务:

ng generate service [服务名称]

例如,要创建一个名为user的服务,可以运行:

ng generate service user

运行上述命令后,Angular CLI会在src/app目录下生成一个user.service.ts文件,以及对应的测试文件user.service.spec.ts。生成的服务类模板如下:

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

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

  constructor() { }

}

2.2 理解@Injectable装饰器

@Injectable是一个装饰器,它用于标记一个类为可注入的服务。这个装饰器接受一个配置对象,其中providedIn属性指定了服务的注入范围。

  • providedIn: 'root':表示该服务在根模块中提供,这是Angular CLI生成服务时的默认配置。这种方式下,服务只会在应用程序启动时创建一个单例实例,整个应用程序都可以共享这个实例。
  • 在特定模块中提供服务:除了在根模块提供服务,还可以在特定的模块中提供服务。例如,假设我们有一个UserModule,可以将服务配置为在这个模块中提供:
import { NgModule } from '@angular/core';
import { UserService } from './user.service';

@NgModule({
  providers: [UserService]
})
export class UserModule { }

这样,UserService就只会在UserModule及其子模块中可用,并且在这个模块的作用域内是单例的。

2.3 服务中的业务逻辑实现

服务类中可以定义各种方法来实现具体的业务逻辑。继续以UserService为例,假设我们要实现一个获取用户信息的方法:

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

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private userUrl = 'api/users';

  constructor(private http: HttpClient) { }

  getUsers() {
    return this.http.get(this.userUrl);
  }
}

在上述代码中,我们注入了HttpClient模块,它是Angular用于处理HTTP请求的服务。getUsers方法使用HttpClientget方法来从指定的URL获取用户数据。

3. 调用Angular服务

3.1 在组件中注入服务

要在组件中使用服务,首先需要将服务注入到组件的构造函数中。以一个UserListComponent为例,假设它需要使用UserService来获取用户列表:

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

@Component({
  selector: 'app-user-list',
  templateUrl: './user-list.component.html',
  styleUrls: ['./user-list.component.css']
})
export class UserListComponent implements OnInit {
  users: any[];

  constructor(private userService: UserService) { }

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

在上述代码中,我们在UserListComponent的构造函数中注入了UserService。在ngOnInit生命周期钩子函数中,调用userService.getUsers()方法来获取用户数据,并通过subscribe方法处理获取到的数据。

3.2 服务的依赖注入原理

Angular的依赖注入系统是基于令牌(Token)的。当我们在组件构造函数中声明一个服务类型的参数(如private userService: UserService)时,Angular会查找与这个类型对应的提供者(Provider)。如果服务是在根模块中提供(providedIn: 'root'),Angular会在根注入器中查找并创建(如果尚未创建)这个服务的单例实例,然后将其注入到组件中。

如果服务是在特定模块中提供,Angular会在该模块的注入器及其祖先注入器中查找提供者。这种分层的注入器结构确保了服务的作用域和正确的实例化。

3.3 跨组件共享服务数据

服务不仅可以用于在单个组件中获取数据,还可以用于在多个组件之间共享数据。例如,我们有一个UserProfileComponentUserListComponent,它们都需要访问当前登录用户的信息。我们可以在UserService中添加一个属性来存储当前用户信息,并提供方法来更新和获取这个信息:

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

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

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

  getCurrentUser() {
    return this.currentUser;
  }
}

UserProfileComponent中,可以更新当前用户信息:

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

@Component({
  selector: 'app-user-profile',
  templateUrl: './user-profile.component.html',
  styleUrls: ['./user-profile.component.css']
})
export class UserProfileComponent implements OnInit {
  newUser = { name: 'John Doe', age: 30 };

  constructor(private userService: UserService) { }

  ngOnInit() {
  }

  updateUser() {
    this.userService.setCurrentUser(this.newUser);
  }
}

UserListComponent中,可以获取当前用户信息:

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

@Component({
  selector: 'app-user-list',
  templateUrl: './user-list.component.html',
  styleUrls: ['./user-list.component.css']
})
export class UserListComponent implements OnInit {
  currentUser;

  constructor(private userService: UserService) { }

  ngOnInit() {
    this.currentUser = this.userService.getCurrentUser();
  }
}

通过这种方式,不同组件可以通过共享的服务来交换数据。

4. 服务的高级用法

4.1 服务中的RxJS操作

在处理异步操作时,Angular服务经常会用到RxJS(Reactive Extensions for JavaScript)。RxJS提供了强大的操作符来处理异步数据流。例如,我们可以对UserServicegetUsers方法返回的Observable进行一些操作:

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

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private userUrl = 'api/users';

  constructor(private http: HttpClient) { }

  getUsers() {
    return this.http.get(this.userUrl).pipe(
      map((users: any[]) => {
        return users.filter(user => user.age > 18);
      })
    );
  }
}

在上述代码中,我们使用了map操作符来过滤出年龄大于18岁的用户。

4.2 服务的依赖注入链

一个服务可以依赖于其他服务。例如,假设我们有一个LoggerService用于记录日志,而UserService需要使用这个日志服务来记录用户相关的操作:

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

@Injectable({
  providedIn: 'root'
})
export class LoggerService {
  log(message: string) {
    console.log(`[LOG] ${message}`);
  }
}
import { Injectable } from '@angular/core';
import { LoggerService } from './logger.service';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  constructor(private logger: LoggerService) { }

  createUser(user) {
    this.logger.log(`Creating user: ${JSON.stringify(user)}`);
    // 实际创建用户的逻辑
  }
}

在这个例子中,UserService依赖于LoggerService,当UserService被注入到组件中时,LoggerService也会被注入到UserService中。

4.3 自定义注入令牌

在某些情况下,我们可能需要使用自定义注入令牌来提供更灵活的依赖注入。例如,假设我们有一个配置对象,不同的环境可能有不同的配置值,我们可以使用自定义注入令牌来提供这些配置:

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

export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');

export interface AppConfig {
  apiUrl: string;
  appName: string;
}

const appConfig: AppConfig = {
  apiUrl: 'https://example.com/api',
  appName: 'My Angular App'
};

在模块中提供这个配置:

import { NgModule } from '@angular/core';
import { APP_CONFIG } from './app.config';

@NgModule({
  providers: [
    { provide: APP_CONFIG, useValue: appConfig }
  ]
})
export class AppModule { }

然后在服务中注入这个配置:

import { Injectable, Inject } from '@angular/core';
import { APP_CONFIG } from './app.config';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  constructor(@Inject(APP_CONFIG) private config: AppConfig) { }

  getUsers() {
    const url = this.config.apiUrl + '/users';
    // 使用url获取用户数据的逻辑
  }
}

通过自定义注入令牌,我们可以更方便地管理和替换配置对象。

5. 服务的测试

5.1 测试服务的基本方法

Angular CLI生成服务时会同时生成一个测试文件(例如user.service.spec.ts)。我们可以使用Jasmine和Karma来编写和运行服务的单元测试。以下是一个简单的UserService测试示例:

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

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

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

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

在上述测试中,我们使用TestBed来配置测试模块,并在其中提供UserServicebeforeEach钩子函数在每个测试用例执行前运行,用于获取服务实例。第一个测试用例检查服务是否成功创建。

5.2 测试服务中的方法

假设UserService有一个addUser方法,我们可以测试这个方法的功能:

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

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

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

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

  it('should add a user', () => {
    const user = { name: 'Jane Doe', age: 25 };
    service.addUser(user);
    const users = service.getUsers();
    expect(users).toContain(user);
  });
});

在这个测试用例中,我们调用addUser方法添加一个用户,然后检查getUsers方法返回的用户列表中是否包含刚刚添加的用户。

5.3 模拟依赖服务

如果UserService依赖于其他服务,比如LoggerService,在测试UserService时,我们可能需要模拟LoggerService以隔离测试。以下是一个模拟依赖服务的示例:

import { TestBed } from '@angular/core/testing';
import { UserService } from './user.service';
import { LoggerService } from './logger.service';

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

  beforeEach(() => {
    mockLoggerService = {
      log: jasmine.createSpy('log')
    };

    TestBed.configureTestingModule({
      providers: [
        UserService,
        { provide: LoggerService, useValue: mockLoggerService }
      ]
    });
    service = TestBed.get(UserService);
  });

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

  it('should call logger when creating user', () => {
    const user = { name: 'Bob Smith', age: 35 };
    service.createUser(user);
    expect(mockLoggerService.log).toHaveBeenCalledWith(`Creating user: ${JSON.stringify(user)}`);
  });
});

在这个测试中,我们创建了一个模拟的LoggerService,并使用useValue将其提供给测试模块。然后在测试createUser方法时,检查LoggerServicelog方法是否被正确调用。

6. 服务与Angular模块的关系

6.1 模块如何提供服务

如前文所述,模块可以通过providers数组来提供服务。例如,在AppModule中提供UserService

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

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

这样,UserServiceAppModule及其子模块中可用。当一个模块提供服务时,它负责创建和管理该服务的实例。

6.2 服务对模块功能的增强

服务可以增强模块的功能。例如,我们有一个SharedModule,其中包含一些通用的组件和服务。SharedModule提供的服务可以被其他导入SharedModule的模块使用,从而扩展这些模块的功能。假设SharedModule提供了一个UtilsService,用于处理一些通用的工具方法:

import { NgModule } from '@angular/core';
import { UtilsService } from './utils.service';

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

AppModule导入SharedModule时,AppModule及其子模块都可以使用UtilsService

import { NgModule } from '@angular/core';
import { SharedModule } from './shared.module';

@NgModule({
  imports: [SharedModule]
})
export class AppModule { }

6.3 延迟加载模块中的服务

在Angular中,我们可以使用延迟加载模块来提高应用程序的性能。延迟加载模块有自己独立的注入器。当一个服务在延迟加载模块中提供时,它在这个模块的注入器中是单例的,并且与其他模块的注入器隔离。

例如,我们有一个延迟加载的AdminModule,其中提供了一个AdminService

import { NgModule } from '@angular/core';
import { AdminService } from './admin.service';

@NgModule({
  providers: [AdminService]
})
export class AdminModule { }

AdminModule被延迟加载时,AdminService会在AdminModule的注入器中创建一个单例实例,这个实例与其他模块中的服务实例相互独立。

7. 服务在大型项目中的应用策略

7.1 服务的分层架构

在大型项目中,采用分层架构来组织服务是一种常见的策略。通常可以分为以下几层:

  • 数据访问层:这一层的服务负责与后端数据源(如数据库、API服务器)进行交互。例如,UserApiService负责处理与用户相关的HTTP请求,获取或更新用户数据。
  • 业务逻辑层:这一层的服务处理业务规则和逻辑。例如,UserLogicService可能会对从UserApiService获取的数据进行处理,如验证数据、计算统计信息等。
  • 共享服务层:包含一些通用的、多个模块或组件可能会用到的服务,如LoggerServiceUtilsService等。

通过分层架构,不同的服务专注于自己的职责,使得代码结构更清晰,易于维护和扩展。

7.2 服务的版本管理

随着项目的发展,服务的接口和功能可能会发生变化。在大型项目中,进行服务的版本管理是很重要的。一种常见的方法是在服务的API URL中添加版本号,例如api/v1/usersapi/v2/users。同时,服务类的方法也可以进行相应的版本控制。例如:

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

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private userUrlV1 = 'api/v1/users';
  private userUrlV2 = 'api/v2/users';

  constructor(private http: HttpClient) { }

  getUsersV1() {
    return this.http.get(this.userUrlV1);
  }

  getUsersV2() {
    return this.http.get(this.userUrlV2);
  }
}

这样,在升级服务时,可以逐步迁移组件对服务的调用,降低升级风险。

7.3 服务的监控与优化

在大型项目中,对服务的监控和优化是确保应用程序性能的关键。可以使用一些工具来监控服务的性能指标,如响应时间、请求频率等。例如,通过在服务中添加日志记录,记录每个请求的开始时间和结束时间,从而计算出响应时间:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { LoggerService } from './logger.service';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private userUrl = 'api/users';

  constructor(private http: HttpClient, private logger: LoggerService) { }

  getUsers() {
    const startTime = new Date().getTime();
    return this.http.get(this.userUrl).pipe(
      map((users: any[]) => {
        const endTime = new Date().getTime();
        const responseTime = endTime - startTime;
        this.logger.log(`Get users response time: ${responseTime} ms`);
        return users;
      })
    );
  }
}

根据监控数据,可以对服务进行优化,如优化数据库查询、调整缓存策略等。

通过以上对Angular服务的构建与调用方法的详细介绍,包括从基础的创建、注入到高级的用法、测试以及在大型项目中的应用策略,希望开发者们能够更深入地理解和运用Angular服务,构建出更加健壮、可维护的前端应用程序。