【问题标题】:Getting and storing a global variable获取和存储全局变量
【发布时间】:2021-02-16 00:44:17
【问题描述】:

我正在为一个简单的概念而苦苦挣扎,我可以在 C# 中轻松实现,但在 Angular 中却不行。

我正在尝试将用户配置文件存储在我在每个组件中添加的服务中,因此每次加载/显示组件时我都不会获取配置文件。

所以,我创建了一个服务,其中构造函数获取用户配置文件,并将其保存在一个变量中,并为我的所有组件使用一个 getter。

我遇到的问题是,当组件调用 getter 时,变量尚未设置,因为服务尚未响应。这会导致未定义的错误并破坏组件,因为配置文件在组件的代码中至关重要。

在 C# 中,我会在获取配置文件时简单地放置 1 等待,以便其他一切都可以运行而无需等待,因为我知道数据在那里。

但在 Angular 中,我尝试过,但它似乎不起作用。

export class SharedService {
  private userProfile;
  constructor(private http: HttpService) {
    this.initProfile();
  }
  async initProfile(): Promise < void > {   
    this.userProfile =await this.http.getProfile().subscribe((data: any) => {
       //...
    }
  });

  getUserProfile() {
    return this.userProfile;
  }
} 

getprofile:

  getProfile() {
    return this.shttp.get(environment.apiEndpoint + 'Getprofile/', {
      headers: new HttpHeaders({
        'Content-Type': 'application/json',
        Accept: '*/*'
      })
    });
  }

在组件中,当我调用 getUserProfile 函数时,服务会立即使用空的 userProfile 进行响应。

编辑: 我设法通过非常讨厌的代码 sn-p 获得所需的行为,但这应该清楚我要完成的工作。在我的 profile.component.ts 中:

async ngOnInit(): Promise<void>  {    
 while (this.profile == null) {
  await this.sharedService.sleep(500);
  this.profile = this.sharedService.getUserProfile();    
 }
 if (this.profile.isadmin){
  this.CalculateStuff();
 }
 //
 //imagine here even more code using the this.profile variable in if statements, 
 //cases, etc.
 //
}

睡眠功能

  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

我必须编写一个睡眠函数来实现这一点,这对我来说是一个明确的信号,表明我做错了。这就是我在这里的原因。

【问题讨论】:

  • 如果你喜欢使用 Promise,请将 observable 转换为 Promise:this.userProfile =await this.http.getProfile().toPromise();应该更好
  • 您不能 await 订阅或可观察并获得结果值,这仅适用于承诺。您可以按照@Andrei 的建议使用.toPromise,或者使用RxJS(Angular 的大部分内容都基于可观察对象,我建议使用后者)。

标签: angular


【解决方案1】:

另一种方法是使服务中的userProfile 成为可观察到的多播(例如带有缓冲区1 的RxJS ReplaySubject)。这样就不需要将 observables 与 Promise 混合在一起。

服务

import { ReplaySubject } from 'rxjs';

export class SharedService {
  private userProfile = new ReplaySubject<any>(1);

  constructor(private http: HttpService) {
    this.initProfile();
  }

  initProfile() {   
    this.http.getProfile().subscribe({
      next: (data: any) => {
        this.userProfile.next(data);           // <-- push the new profile
      },
      error: (error: any) => { }               // <-- handle error
  });

  getUserProfile(): Observable<any> {          // <-- return observable here
    return this.userProfile.asObservable();
  }
}

现在在组件中,您可以从服务订阅主题。此外,您可以使用 takeUntil 运算符和 Subject 在组件关闭/销毁时关闭打开的订阅。

组件

import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

export class SomeComponent implements OnInit, OnDestroy {
  public userProfile: any;
  private closed$ = new Subject<any>();

  constructor(private shared: SharedService) { }

  ngOnInit() {
    this.shared.getUserProfile().pipe(
      takeUntil(this.closed$)
    ).subscribe({
      next: (data: any) => {
        this.userProfile = data;
        // other statements that depend on `this.userProfile`
      }
    });
  }

  ngOnDestroy() {
    this.closed$.next();   // <-- close open subscription(s)
  }
}

【讨论】:

  • 我绝对需要在 rxjs 上投入一些(很多?)时间,这对我来说听起来像是一门外语 :( 但是谢谢你写出来。我会在我回来的时候回来更好地掌握 rxjs。希望很快!
  • @Hedi:虽然RxJS docs 可以提供有价值的参考,但我不建议将其作为起点。它们是旨在补充现有知识的生硬信息。相反,您可以开始使用learnrxjs.io。它们为每个函数和运算符提供更多上下文,并提供易于掌握的示例。您也可以使用 rxmarbles.com 来补充 LearnRxJS。
  • 再次感谢您提供的信息。我会为您的评论添加书签,并在启动 RXJS 时返回!
【解决方案2】:

你快到了。您正在等待的订阅不适用于 async / await。只需执行以下操作。只需将其转换为承诺,以便您可以await 它。

async ngOnInit(): Promise<void>  {  
  this.profile = await this.sharedService.getUserProfile().toPromise();    
 if (this.profile.isadmin){
  this.CalculateStuff();
 }
}

这个问题类似How exactly works this async-await method call? is it correct?

为您提供更多细节。您正在做的是将this.userProfile 分配给RXJS 订阅对象。这不会为您提供所需的价值。如果您想按照其中一个 cmets 中的建议继续使用 Rxjs。您可以在订阅块内为用户配置文件执行所有逻辑。

  this.http.getProfile().subscribe((userProfile: any) => {
       // do userProfile actions here
  });
  //Since subscribe is an async callback, anything outside that block will have userProfile undefined.

同样如前所述,RxJs 的基本知识将非常有用,可以解决您的许多早期问题。 https://www.learnrxjs.io/learn-rxjs/concepts/rxjs-primer

【讨论】:

  • 感谢您的回复。我试图避免将我所有的代码都放在订阅答案中。如前所述,很多代码都依赖于配置文件的内容,所以,在发布之前,这就是我正在做的事情。只要未在服务中设置用户配置文件,就不能简单地停止代码执行流程吗?因为其他一切都取决于它。还是我错过了什么?如果您需要值在代码中做出决策,您如何使用 Promise?
  • 是的,我的答案中的第一块代码可以做到这一点(订阅也可以,但有点复杂)。当您将 observable 转换为 Promise 并 await 时。您的函数会一直等到 promise 完成(成功或抛出错误)。承诺将完成,然后您可以使用this.userProfile,就像下面几行中的“正常”变量一样。
  • 对不起,是的,你是对的,代码块正在停止。我的问题表述得很糟糕。我的意思是,我的组件似乎没有等待代码执行完毕。它将从共享服务中获取未定义/未设置的用户配置文件,即使共享服务仍在“等待”来自 http 服务的 getprofile。
  • 啊,是的。这是 Angular 中异步代码的基本部分。您需要使用 ngIf="userProfile" 对模板进行空检查(这不是必需的),以便不尝试对空值执行任何操作。当配置文件承诺完成时,该值将不再为空,并且模板将为新的“非空”配置文件执行更新。您能否提供一个代码示例说明究竟是什么导致了问题?
  • 好的。再次感谢您的宝贵时间!
猜你喜欢
  • 2011-10-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-18
  • 1970-01-01
  • 1970-01-01
  • 2016-04-24
相关资源
最近更新 更多