如何在普通TypeScript文件的导出函数内调用Angular服务
普通TypeScript文件中如何直接调用Angular DI管理的服务
问题描述
我不确定该需求是否可实现:位于非Angular组件(例如独立的普通TypeScript文件)中的导出函数,能否在函数内部调用Angular服务的方法?
示例场景如下,首先是Angular DI容器管理的服务:
import { Injectable } from '@angular/core'; import { HttpClient } from "@angular/common/http"; import { Observable } from "rxjs"; import { Contact } from "@comp-store/data-model"; const URL = 'http://localhost:3333/api/contacts'; @Injectable({ providedIn: 'root' }) export class ContactsService { constructor(private http: HttpClient) {} sayHello(): string { return "hello there!"; } all(): Observable<Contact[]> { return this.http.get<Contact[]>(URL); } }
现有一个普通TS文件utils.ts,内容如下:
export const callTheService = () => { console.log('==> about to call the service...'); // 此处需要直接获取ContactsService实例,不通过参数传入 // 目标1:调用sayHello()方法 // 目标2:调用all()方法获取联系人列表 }
需求是直接调用callTheService()即可触发服务调用逻辑,不通过函数参数传入服务实例。
补充思考:我原本认为该方案不可行,因为手动DI需要在Angular组件/模块上下文中编写,例如自定义provider的逻辑必须写在
@Component或@NgModule的装饰器参数里,普通TS文件没有这个上下文,似乎无法实现DI。示例手动DI代码如下:function contactServiceProvider(http: HttpClient): ContactsService { return new ContactsService(http); } export const CONTACT_SERVICE = new InjectionToken<ContactsService>('CONTACT_SERVICE'); @Component({ selector: 'comp-store-header', templateUrl: './header.component.html', styleUrls: ['./header.component.scss'], providers: [ { provide: CONTACT_SERVICE, useFactory: contactServiceProvider, deps: [HttpClient] } ] }) export class HeaderComponent { constructor(@Inject(CONTACT_SERVICE) private service: ContactsService) {} // ... }据此判断普通TS文件中无法编写这类DI逻辑。
解答
这个需求完全可以实现,不需要手动构建服务的依赖链,核心思路是在Angular应用启动时缓存根注入器实例,之后在任意普通TS文件中通过该注入器获取已注册的服务,具体实现步骤如下:
新建全局注入器存取文件
新建app-injector.ts,用来存应用启动后的根注入器引用:import { Injector } from '@angular/core'; let rootInjector: Injector; /** * 应用启动时调用,缓存根注入器实例 */ export const setRootInjector = (injector: Injector) => { rootInjector = injector; }; /** * 任意位置调用,获取根注入器 */ export const getRootInjector = () => rootInjector;应用启动时注入根注入器
根据你用的Angular启动模式,选择对应的方式缓存注入器:- NgModule模式:在根模块
AppModule中实现DoBootstrap接口,存入注入器import { NgModule, Injector, DoBootstrap } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { setRootInjector } from './app-injector'; // 其他业务模块、组件导入 @NgModule({ imports: [BrowserModule, HttpClientModule /* 其他依赖 */], declarations: [/* 根组件等 */], bootstrap: [/* 根组件 */] }) export class AppModule implements DoBootstrap { constructor(private injector: Injector) {} ngDoBootstrap() { setRootInjector(this.injector); } } - Standalone模式(Angular 14+):在
main.ts启动应用后存入注入器import { createApplication } from '@angular/platform-browser'; import { setRootInjector } from './app-injector'; import { AppComponent } from './app/app.component'; createApplication({ providers: [/* 全局provider配置 */] }).then(appRef => { setRootInjector(appRef.injector); appRef.bootstrap(AppComponent); });
- NgModule模式:在根模块
普通TS文件中直接获取服务调用
修改utils.ts,不需要传参,直接通过根注入器拿服务实例:import { getRootInjector } from './app-injector'; import { ContactsService } from './contacts.service'; export const callTheService = () => { console.log('==> about to call the service...'); const contactsService = getRootInjector().get(ContactsService); // 调用sayHello方法 console.log(contactsService.sayHello()); // 调用all方法,注意Observable需要订阅才会发起请求 contactsService.all().subscribe({ next: (contacts) => console.log('获取联系人成功', contacts), error: (err) => console.error('获取联系人失败', err) }); }
注意事项
- 该方案仅对全局注册的服务生效:包括
providedIn: 'root'的服务、根模块/启动时全局配置的provider里的服务。组件级providers注册的服务属于组件注入上下文,本身就不应该在脱离组件的普通TS文件中调用。 - 调用时机限制:必须等Angular应用启动完成、
setRootInjector执行完毕后再调用callTheService,否则会因为拿不到注入器实例报错。不要在Angular初始化流程的顶层(比如模块构造函数未执行完时)直接调用该方法。 - 不需要手动构建依赖:你之前认为不可行,是因为误以为要在普通TS里自己new服务、自己组装
HttpClient这类依赖,实际上我们直接复用Angular已经初始化完成的根注入器,所有依赖关系Angular已经处理完毕,不需要手动实例化任何依赖。
内容的提问来源于stack exchange,提问作者mohsenmadi
相关产品推荐
相关产品推荐

