【问题标题】:Making an angular service's property available throughout the application使 Angular 服务的属性在整个应用程序中可用
【发布时间】:2022-01-23 11:51:37
【问题描述】:

我有一个 Angular 应用程序,我想将 JSON 文件中的一堆变量挂载到 configService 中,Angular 应用程序从它自己的 Web 服务器获取,所以我可以将它作为 configMap 注入到容器中。

然后我希望这个服务和服务的配置成员属性在整个应用程序中都可以访问。

这是我现在所拥有的:

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

  configUrl = '/config/cfg.json';
  public config: Config | undefined;
  constructor(private httpClient: HttpClient) {
  }

  getConfig(): Observable<Config> {
    return this.httpClient.get<Config>(window.location.origin + this.configUrl)
      .pipe(
        catchError(this.handleError<Config>("getConfig", this.config = { apiBaseUrl: "", adminLoginUrl: "", clientLoginUrl: "" }))
      )
  }
  getConfigObject() : Config | undefined{
    if (this.config?.apiBaseUrl == undefined) {
      this.getConfig().subscribe((data: Config) => this.config = {
        apiBaseUrl: data.apiBaseUrl,
        adminLoginUrl: data.adminLoginUrl,
        clientLoginUrl: data.clientLoginUrl
      })

    }
    return this.config;
  }
//error handling omitted
}

在 getConfig() 方法上调用 subscribe 时,我最终得到了数据,但我不能使用它立即在该 apiBaseUrl 上调用一个函数,然后它是“未定义的”。 当我尝试调用 getConfigObject() 时,它不会返回对象,至少当时不会。

我想要做的是在调用我的后端 api 时使用这个 config.apiBaseUrl。 所以我可以在另一个服务/组件/模块中做到这一点:

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

  config: Config | undefined;
  invoices: InvoiceItem[] | undefined;
  confSub!: Subscription;
  invSub!: Subscription;
//Idea 1, set the local config object in the constructor, does not work:
constructor(private configSvc: ConfigService, private httpClient: HttpClient) {
    this.config = this.configSvc.getConfigObject()
  }

getInvoices(): Observable<InvoiceItem[]> {
    this.config = this.configSvc.config
    console.log(this.config?.apiBaseUrl + "api/invoices");
    //This above log results in just "api/invoices" in the console so the apiBaseUrl isn't loaded yet.
    return this.httpClient.get<InvoiceItem[]>(this.config?.apiBaseUrl + "api/invoices")
      .pipe(
        catchError(this.handleError("getInvoices", this.invoices = []))
      );
  }

//idea 2: Get the configObject in the ngOnInit() (which I have, maybe wrongfully, added to a service) by calling the method that returns an observable. 
  ngOnInit(): void {
    this.confSub = this.configSvc.getConfig()
      .subscribe({
        next: (data: Config) => this.config = data,
        error: (err: Error) => console.log("Could not get config, " + err)
      });
  }

我昨天通过嵌套调用 configSvc.getConfig() 和 invoiceService.getInvoices() 不知何故让它工作了,但这导致发票端点发送垃圾邮件,这不是我期望使用的解决方案。

根据角度文档,可注入服务应该是单例,这让我期望调用 this.getConfig() 的 ngOnInit() 方法会用值填充实例变量“config”,然后这个实例将是可通过将其作为private configSvc: ConfigService 注入构造函数然后调用this.configSvc.config.apiBaseUrl 来访问和使用,但这也不起作用,它返回未定义。

我正在使用最新的稳定 Angular 版本。

我错过了什么?

编辑: 我知道里面:

this.confSub = this.configSvc.getConfig()
      .subscribe({
        next: (data: Config) => {
          //HERE
          this.config = data,
        error: (err: Error) => console.log("Could not get config, " + err)
      });

配置数据是可访问的,理论上我可以调用后端来获取发票,但我不能将其作为可观察的 getInvoices 函数返回。 这不起作用:

return this.configSvc.getConfig()
      .subscribe({
        next: (data: Config) => {
          this.config = data;
          return this.httpClient.get<Config>(window.location.origin + this.configUrl)
            .pipe(
              catchError(this.handleError<Config>("getConfig", this.config = { apiBaseUrl: "", adminLoginUrl: "", clientLoginUrl: "" }))
      )
        },
        error: (err: Error) => console.log("Could not get config, " + err)
      });

因为它会返回 config observable 而不是 InvoiceItem[] observable。 我将在多个地方需要后端 api url,所以我只想能够注入它并像这样使用它:

constructor(private httpClient: HttpClient, private configSvc: ConfigService) {}
...
return this.httpClient.get<something>(this.configSvc.config?.apiBaseUrl + "/some/endpoint")

就像一个在启动时挂载并在整个生命周期中保持活动和可访问的单例对象。 每次我想从后端端点获取东西时嵌套调用 configService 似乎是不必要的,或者我不太明白这是如何工作的。

【问题讨论】:

标签: angular kubernetes


【解决方案1】:

就像Edvin M. Cruz 提到的那样,我使用Subject 制作了这个变通解决方案,以在嵌套函数中获取一个 Observable:

  getInvoices(): Observable<InvoiceItem[]> {
    var subject = new Subject<InvoiceItem[]>();
    this.configSvc.getConfig().subscribe({
        next: (data: Config) => {

            this.httpClient.get<InvoiceItem[]>(data.apiBaseUrl +"api/invoices")
            .pipe(
              catchError(this.handleError("getInvoices", this.invoices = []))
            ).subscribe({
              next: (invs: InvoiceItem[]) => {
                subject.next(invs);
              }
            });
        }
      });
    return subject.asObservable()
  }

我仍然觉得应该有一种方法可以让 ConfigService 成为一个单例实例,我可以在任何地方注入它并从中获取后端 api url。

编辑:这就是我最终解决的方法。

也许这不是最佳实践,但它可以工作,并且在客户端加载应用程序时只加载一次配置,并且它可用于整个应用程序中的其他服务。如果有人愿意贡献,我愿意接受改进它的建议。

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

  configUrl = '/config/cfg.json';
  public config: Config | undefined;
  constructor(private httpClient: HttpClient) {
    this.getConfig().subscribe({
      next: (data: Config) => {
        this.config = data
      },
      error: (err) => this.handleError<Config>("constructor",this.config = { apiBaseUrl: "", adminLoginUrl: "", clientLoginUrl: "" })
    })
  }

  getConfig(): Observable<Config> {
    return this.httpClient.get<Config>(window.location.origin + this.configUrl)
      .pipe(
        catchError(this.handleError<Config>("getConfig", this.config = { apiBaseUrl: "", adminLoginUrl: "", clientLoginUrl: "" }))
      )
  }
}

如果我需要另一个服务中的 apiBaseUrl,我会这样做(configService 现在已在启动期间急切地解决,并且配置值是可访问的,并且每次客户端应用程序加载仅获取一次):

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

  config: Config | undefined;
  constructor(private configSvc: ConfigService, private http: HttpClient) { }

  getInvoices(): Observable<InvoiceItem[]> {

    return this.http.get<InvoiceItem[]>(this.configSvc?.config?.apiBaseUrl + "api/invoices")
    .pipe(
      catchError(this.handleError("getInvoiceItems", this.invoices = []))
    );
//...
}

这是我的推理: 在这种特殊情况下,我可以偏离从服务返回可观察对象的模式,因为它是每个应用程序案例一次,其中应用程序配置从其自己的 Web 服务器加载。在所有其他服务中(如上面的发票示例),我会返回一个应该做的 observable。通过这种方式,我可以将包含当前环境后端 url 的 ConfigMap 卷挂载到 kubernetes 中的容器中:

apiVersion: v1
data:
  cfg.json: |
    {
        "apiBaseUrl": "http://backend.site.url:5000/",
        "adminLoginUrl": "https://login-admin.example.com",
        "clientLoginUrl": "https://login-client.example.com"
    }
kind: ConfigMap
metadata:
  creationTimestamp: null
  name: frontend-config

然后挂载它:

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: app-name
  name: app-name
spec:
  replicas: 3
  selector:
    matchLabels:
      app: app-name
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: app-name
    spec:
      containers:
      - image: docker.io/repo/image:tag
        imagePullPolicy: Always
        name: app-name
        resources: {}
        volumeMounts:
        - name: config
          mountPath: /usr/share/nginx/html/config
      volumes:
      - name: config
        configMap:
          name: frontend-config
          items:
          - key: cfg.json
            path: cfg.json
status: {}

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2016-08-01
  • 2023-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-06
相关资源
最近更新 更多