根据服务器是否运行注入不同的服务

Inject different services based on whether the server is running

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

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

我尝试使用 APP_INITIALIZER 注入令牌,这样我就可以在加载应用程序之前“ping”服务器并在选择提供程序时测试结果。 (这段代码既是on Github and can also be viewed in this Stackblitz.

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

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

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

有没有其他方法可以做到这一点。我是否可以在 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;
  }
}

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

Here my solution on Stackblitz

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

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();
  }
}