面试题答案
一键面试在根模块提供服务
- 原理:在根模块(通常是
AppModule
)的providers
数组中注册服务。Angular 的依赖注入系统会创建该服务的单一实例,并在整个应用中共享此实例。当任何组件或模块需要该服务时,依赖注入系统会提供此单一实例。 - 优点
- 全局唯一实例:确保整个应用中使用的是同一个服务实例,这对于需要维护全局状态的服务非常重要,比如用户认证服务。
- 简单直接:配置简单,在根模块一次性注册,所有模块都可使用。
- 缺点
- 可能导致模块间耦合:如果服务在根模块提供,所有模块都依赖这个单一实例,可能使得模块之间的耦合度变高,不利于模块的独立测试和复用。
- 加载时机问题:由于在根模块注册,服务会随着根模块一起加载,如果服务较复杂或包含大量初始化逻辑,可能影响应用启动性能。
- 适用场景:适用于那些应用级别的服务,例如日志记录服务、全局配置服务等,这些服务需要在整个应用中保持单一实例且被广泛使用。
在共享模块提供服务
- 原理:创建一个共享模块,将需要共享的服务在该共享模块的
providers
数组中注册。然后,其他需要使用该服务的模块导入这个共享模块。这样,依赖注入系统会为导入共享模块的每个模块创建该服务的一个实例(如果模块之间是独立加载的),或者如果共享模块是延迟加载的,也会在共享模块加载时创建一个单一实例供导入它的模块使用。 - 优点
- 提高模块复用性:共享模块可以被多个模块导入,使得共享服务的复用性提高,同时减少重复代码。
- 降低模块耦合:相比在根模块提供服务,各模块通过共享模块使用服务,耦合度相对较低,更利于模块的独立开发和测试。
- 缺点
- 可能产生多个实例:如果共享模块被多次导入(非延迟加载且模块独立加载场景),可能会导致服务创建多个实例,这对于需要全局唯一状态的服务是不利的。
- 管理复杂度增加:需要额外管理共享模块,包括模块的导出、导入等操作,增加了一定的管理成本。
- 适用场景:适用于那些多个功能模块都可能需要使用,但又不需要严格保持全局唯一实例的服务,比如一些通用的工具服务,如日期格式化服务、字符串处理服务等。
解决依赖注入相关问题的示例
假设我们有一个 UserService
用于管理用户信息,并且在多个模块间共享。
- 在根模块提供服务的示例
- 创建服务:
import { Injectable } from '@angular/core';
@Injectable()
export class UserService {
private user: any;
constructor() {}
setUser(user: any) {
this.user = user;
}
getUser() {
return this.user;
}
}
- 在根模块(
AppModule
)注册:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { AppComponent } from './app.component';
import { UserService } from './user.service';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule],
providers: [UserService],
bootstrap: [AppComponent]
})
export class AppModule {}
- 在组件中使用:
import { Component } from '@angular/core';
import { UserService } from './user.service';
@Component({
selector: 'app - my - component',
templateUrl: './my - component.html'
})
export class MyComponent {
constructor(private userService: UserService) {}
ngOnInit() {
this.userService.setUser({ name: 'John' });
console.log(this.userService.getUser());
}
}
- 在共享模块提供服务的示例
- 创建共享模块:
import { NgModule } from '@angular/core';
import { UserService } from './user.service';
@NgModule({
providers: [UserService],
exports: []
})
export class SharedModule {}
- 在其他模块导入共享模块:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MyFeatureComponent } from './my - feature.component';
import { SharedModule } from './shared.module';
@NgModule({
declarations: [MyFeatureComponent],
imports: [CommonModule, SharedModule],
exports: [MyFeatureComponent]
})
export class MyFeatureModule {}
- 在组件中使用:
import { Component } from '@angular/core';
import { UserService } from './user.service';
@Component({
selector: 'app - my - feature - component',
templateUrl: './my - feature - component.html'
})
export class MyFeatureComponent {
constructor(private userService: UserService) {}
ngOnInit() {
this.userService.setUser({ name: 'Jane' });
console.log(this.userService.getUser());
}
}
为了解决依赖注入相关问题,比如确保服务实例的唯一性:
- 对于根模块提供服务:要注意避免在不同模块中意外重新创建服务实例,确保所有对服务的使用都是通过依赖注入获取同一个实例。
- 对于共享模块提供服务:如果需要确保唯一性,可以结合延迟加载技术,使得共享模块只被加载一次,从而保证服务实例唯一。或者使用
forRoot
模式在共享模块中实现单例服务,如下:- 修改共享模块:
import { NgModule, Optional, SkipSelf } from '@angular/core';
import { UserService } from './user.service';
@NgModule({
providers: []
})
export class SharedModule {
constructor(@Optional() @SkipSelf() parentModule: SharedModule) {
if (parentModule) {
throw new Error(
'SharedModule is already loaded. Import it in the AppModule only'
);
}
}
static forRoot() {
return {
ngModule: SharedModule,
providers: [UserService]
};
}
}
- 在根模块导入:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { AppComponent } from './app.component';
import { SharedModule } from './shared.module';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, SharedModule.forRoot()],
bootstrap: [AppComponent]
})
export class AppModule {}
这样在根模块通过 SharedModule.forRoot()
导入共享模块,确保服务在整个应用中只有一个实例,同时其他模块导入共享模块时不会重复创建服务实例。