You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular 18中如何等待HttpClient订阅完成后再返回数据

问题描述

我正在使用Angular 18,尝试从API查询数据并存储到组件中。能在组件和数据获取服务中通过console.log成功打印存储API数据的对象,但单独打印对象的键或值时控制台显示undefined,使用Object.keys处理该对象也返回空数组。

我发现控制台展开的对象未准确反映当前值,但不确定修复方法。尝试延迟订阅返回数据以在其他代码执行前存入变量,但方法无效;让对象必须使用this关键字也无帮助。我尽量不想使用async/await(不想处理Promise),但无其他办法也可接受。

核心问题:如何等待HttpClient订阅完成后,再返回其查询的API数据?

原代码

api.service.ts

export class ApiService {
  constructor(private httpClient: HttpClient) { }
  private collection: Collection = { cards: {} };

  queryApi(query: string): Observable<Data> {
    return this.httpClient.get<Data>(`https://api.scryfall.com/${query}`);
  }

  retrieveCard(name: string, set?: string): Cards {
    let card: Cards = {}

    this.queryApi(`cards/named?exact=${name}${typeof set != undefined ? `&set=${set}` : ""}`)
    .subscribe((json) => {
      card[json["id"]] = {
        name: json["name"],
        qty: 1,
        set: json["set"],
        imageURL: json["image_uris"]["png"]
      }
    });

    return card;
  }
}

add-cards.component.ts

export class AddCardsComponent {
  constructor(private apiService: ApiService) {}
  private tempCollection: Collection = { cards: {} };
  name: string = "";
  set: string = "";

  addTempCard(): void {
    const card: Cards = this.apiService.retrieveCard(this.name.toLowerCase(), this.set?.toLowerCase());
    console.log(card) // 返回键和值
    console.log(Object.keys(card)[0]) // 返回undefined

    this.name = "";
    this.set = "";
  }
}
解决方案

问题根源在于retrieveCard是同步返回空对象,但API请求是异步的——subscribe里的代码要等API响应回来才执行,此时方法已经提前返回了空的card,后续操作自然拿不到数据。下面提供两种修复方式:

方式一:返回Observable(推荐,符合Angular异步规范)

让服务层返回Observable,把API请求逻辑封装在数据流中,组件层订阅后再处理数据:

修改后的api.service.ts

export class ApiService {
  constructor(private httpClient: HttpClient) { }
  private collection: Collection = { cards: {} };

  queryApi(query: string): Observable<Data> {
    return this.httpClient.get<Data>(`https://api.scryfall.com/${query}`);
  }

  retrieveCard(name: string, set?: string): Observable<Cards> {
    // 修正原参数判断逻辑的bug
    const queryParams = `cards/named?exact=${name}${set ? `&set=${set}` : ""}`;
    
    return this.queryApi(queryParams).pipe(
      map(json => {
        const card: Cards = {};
        card[json["id"]] = {
          name: json["name"],
          qty: 1,
          set: json["set"],
          imageURL: json["image_uris"]["png"]
        };
        return card;
      })
    );
  }
}

修改后的add-cards.component.ts

export class AddCardsComponent {
  constructor(private apiService: ApiService) {}
  private tempCollection: Collection = { cards: {} };
  name: string = "";
  set: string = "";

  addTempCard(): void {
    this.apiService.retrieveCard(this.name.toLowerCase(), this.set?.toLowerCase())
      .subscribe(card => {
        console.log(card); // 此时能拿到完整数据
        console.log(Object.keys(card)[0]); // 不再是undefined
        
        // 将card存入临时集合
        Object.assign(this.tempCollection.cards, card);
      });

    this.name = "";
    this.set = "";
  }
}

方式二:使用async/await(转Promise)

如果想要同步写法的体验,可以把Observable转成Promise,用async/await处理:

修改后的api.service.ts

export class ApiService {
  constructor(private httpClient: HttpClient) { }
  private collection: Collection = { cards: {} };

  queryApi(query: string): Observable<Data> {
    return this.httpClient.get<Data>(`https://api.scryfall.com/${query}`);
  }

  async retrieveCard(name: string, set?: string): Promise<Cards> {
    const queryParams = `cards/named?exact=${name}${set ? `&set=${set}` : ""}`;
    const json = await this.queryApi(queryParams).toPromise();
    
    const card: Cards = {};
    card[json["id"]] = {
      name: json["name"],
      qty: 1,
      set: json["set"],
      imageURL: json["image_uris"]["png"]
    };
    return card;
  }
}

修改后的add-cards.component.ts

export class AddCardsComponent {
  constructor(private apiService: ApiService) {}
  private tempCollection: Collection = { cards: {} };
  name: string = "";
  set: string = "";

  async addTempCard(): Promise<void> {
    const card: Cards = await this.apiService.retrieveCard(this.name.toLowerCase(), this.set?.toLowerCase());
    console.log(card);
    console.log(Object.keys(card)[0]);
    
    Object.assign(this.tempCollection.cards, card);

    this.name = "";
    this.set = "";
  }
}

关键说明

  • 控制台展开对象显示数据是因为浏览器会延迟解析对象引用——你看到的是对象最终状态,而非console.log执行时的状态,这就是直接打印键会得到undefined的原因。
  • Angular中优先推荐使用Observable处理异步操作,它比Promise更灵活,支持取消、重试、组合等操作,更适配Angular生态。

内容的提问来源于stack exchange,提问作者bascott

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 10:57:19