【问题标题】:Inject different services based on whether the server is running根据服务器是否运行注入不同的服务
【发布时间】:2021-03-10 02:24:58
【问题描述】:

我想在后端服务器关闭时自动提供“存根”服务。目前我通过手动更改配置设置“isRunning”来做到这一点。但出于多种原因,我希望在不编辑代码或配置文件的情况下完成此检查。

let isRunning: boolean = true;
...
  providers: [
    {
      provide: MyService,
      useClass: isRunning ?  MyService : MyServiceStub
    },
    ...
],

我尝试使用 APP_INITIALIZER 注入令牌,以便在加载应用程序之前“ping”服务器并在选择提供程序时测试结果。 (此代码既是on Github,也可以查看in this Stackblitz

function IsRunning(){
  return AppInitService.isWebServerRunning();
}
...
  providers: [
    {
      provide: MyService,
      useClass: IsRunning() ?  MyService : MyServiceStub
    }
],

但我遇到的问题是“isRunning()”在框架已经选择并将 MyService 添加到依赖注入器之后返回其结果。因此,即使上面的“isRunning()”返回 false,它仍然选择 MyService 而不是 MyServiceStub。

如果您运行 Stackblitz,您可以从控制台日志中看到这是真的。 MyService 构造函数中的 console.log 语句在 IsRunning() 返回之前输出。

还有其他方法可以做到这一点。我是否可以在 IsRunning() 函数中包含手动注入提供程序而不是依赖于 @NgModule 中的提供程序数组的代码。

APP_INITIALIZER 令牌及其提供服务的工厂在 AppInitModule 中实现:

export function pingFactory(appInitService: AppInitService) {
  return () => appInitService.pingServer();
}

@NgModule({
  imports: [HttpClientModule],
  providers: [
    AppInitService,
    { provide: APP_INITIALIZER, useFactory: pingFactory, deps: [AppInitService], multi: true },
  ],
})
export class AppInitModule {}

这里是 AppInitService 的代码:

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

  constructor(private httpClient: HttpClient) {}

  static isRunning: boolean | null = null;

  static async isWebServerRunning() {
    logMsg("isWebServerRunning Enter");
    const delay = (ms) => new Promise((res) => setTimeout(res, ms));

    // check every 50 milliseconds for change in "isRunning" status
    while (AppInitService.isRunning === null) {
      logMsg("isWebServerRunning in delay loop");
      await delay(50);
    }
    let isrunning = AppInitService.isRunning;
    logMsg("isWebServerRunning Exit -isRunnning = " + String(isrunning));
    return isrunning;
  }

  pingServer(): Promise<any> {
    logMsg("pingServer. Enter");

    const promise = this.httpClient
      .get(server)
      .toPromise()
      .then((settings) => {
        logMsg("pingServer Got server response");
        AppInitService.isRunning = true;
        return true;
      })
      .catch((err) => {
        logMsg("pingServer No server Response");
        AppInitService.isRunning = false;
        err;
      });
    return promise;
  }
}

【问题讨论】:

    标签: angular typescript dependency-injection


    【解决方案1】:

    我检查了 stackblitz 示例,发现您在 appinit.module.ts 中有 APP_INITIALIZER,但必须将其导入根模块 (app.module.ts) 并重构您的一些代码。我为服务创建了一个管理器模块以及一个工厂,以便根据我们的条件初始化服务器。

    Here my solution on Stackblitz

    我添加了一些文件夹,解释在每个文件中都写为 cmets。

    app.module.ts

    // imports omitted...
    
    export function pingFactory(appInitService: AppInitService) {
      return () => appInitService.pingServer();
    }
    
    @NgModule({
      declarations: [AppComponent],
      // MyServiceManagerModule must be imported calling forRoot on our root module 
      // this module internally handle which service will be used
      imports: [BrowserModule, HttpClientModule, MyServiceManagerModule.forRoot()],
      providers: [
        // our APP_INITIALIZER must be imported on our root module too
        {
          provide: APP_INITIALIZER,
          useFactory: pingFactory,
          deps: [AppInitService],
          multi: true
        }
      ],
      bootstrap: [AppComponent]
    })
    export class AppModule {}
    

    my-service-manager.module.ts

    // imports omitted...
    
    // this factory needs AppInitService to know if our web server is running
    // in order to select our service
    export function myServiceFactory(
      appInitService: AppInitService
    ): MyService | MyServiceStub {
      return appInitService.isRunning ? new MyService() : new MyServiceStub();
    }
    
    @NgModule()
    export class MyServiceManagerModule {
      static forRoot(): ModuleWithProviders<MyServiceManagerModule> {
        return {
          ngModule: MyServiceManagerModule,
          providers: [
            // our service factory
            {
              provide: MyServiceLoader,
              useFactory: myServiceFactory,
              deps: [AppInitService]
            }
          ]
        };
      }
    }
    
    

    my-service-loader.ts

    // imports omitted...
    
    // our abstract class
    @Injectable()
    export abstract class MyServiceLoader {
      abstract printTime(): void;
      abstract getNow(): string;
    }
    

    my-service-stub.ts

    // imports omitted...
    
    // must extend MyServiceLoader which is an abstract class
    @Injectable()
    export class MyServiceStub extends MyServiceLoader {
      printTime(): void {
        let time = this.getNow();
        console.log("MyServiceStub:printTime ", time);
      }
    
      getNow(): string {
        let now = Date.now();
        let sec = Math.floor(now / 1000) % 100;
        let ms = now % 1000;
        return sec.toString() + ":" + ms.toString();
      }
    }
    
    

    my-service.ts

    // imports omitted...
    
    // must extend MyServiceLoader which is an abstract class
    @Injectable()
    export class MyService extends MyServiceLoader {
      printTime(): void {
        let time = this.getNow();
        console.log("MyService:printTime ", time);
      }
    
      getNow(): string {
        let now = Date.now();
        let sec = Math.floor(now / 1000) % 100;
        let ms = now % 1000;
        return sec.toString() + ":" + ms.toString();
      }
    }
    
    

    app.component.ts

    import { Component } from "@angular/core";
    import { MyServiceLoader } from "./my-service/my-service-loader";
    
    @Component({
      selector: "app-root",
      template: `
        <h2>Test for server running</h2>
      `
    })
    export class AppComponent {
      title = "testasync";
    
      // MyServiceLoader must be imported
      constructor(private myService: MyServiceLoader) {
        console.log("AppComponent:ngOnInit", this.getNow());
        this.myService.printTime();
      }
    
      getNow(): string {
        let now = Date.now();
        let sec = Math.floor(now / 1000) % 100;
        let ms = now % 1000;
        return sec.toString() + ":" + ms.toString();
      }
    }
    

    【讨论】:

    • 太棒了,谢谢。我可以通过更改服务器 URL 并看到它切换到其他服务来看到它的工作原理。但是,现在我需要详细阅读您的代码并了解“为什么”。 (加上阅读更多 Angular 文档)
    • 太棒了!很高兴为您提供帮助
    • 由于 myServiceFactory 使用“new”来创建 MyService(和存根),如果 MyService 也有 DI 依赖项,这将不起作用。你知道如何解决这个问题吗? [当我第一次注意到这一点时,我尝试向 MyService 添加一个构造函数和依赖项,它似乎可以工作。但这只是因为 Stackblitz 没有给出编译错误。当我将代码移到我的项目中时它失败了。 ]
    • 是的,我会尽量把它添加到stackblitz的例子中
    • 我做到了。再次查看 stackblitz 上的示例
    猜你喜欢
    • 1970-01-01
    • 2016-08-16
    • 2016-01-11
    • 1970-01-01
    • 2016-07-10
    • 2023-04-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-25
    相关资源
    最近更新 更多