【问题标题】:OnInit retrieving Service property which is not yet loadedOnInit 检索尚未加载的服务属性
【发布时间】:2026-01-24 23:20:03
【问题描述】:

我对 Angular 2+ 非常陌生(大约一周),我对可观察部分有疑问。

我有这个服务:

export class GetProductsService {

  allProducts;

  constructor(private http: HttpClient) {
    this.http.get('https://jsonplaceholder.typicode.com/posts').subscribe({
      next: data => this.allProducts = data,
      error: err => console.log(err)
    });
  }

  getAllProducts() {
    return this.allProducts;
  }

在构造函数中,我发出一个获取请求以检索一些假“产品”。

我检索到的产品,我存储在一个属性中,以便将来我可以更轻松地使用该属性上的其他功能。

现在这是我的问题:我有这个组件

export class AllProductsComponent implements OnInit {

allProducts: any;

constructor(private http: HttpClient, private productsService: GetProductsService) {}

ngOnInit(): void {
    this.allProducts = this.productsService.getAllProducts();
  }


}

从 ProductService OnInit 中检索 products 属性,但是考虑到它是一个 observable,当其他组件尝试检索 allProducts 属性时,它还没有从 observable 流中完全加载。

如何解决这个问题,我可以看到这是一个常见的问题,这就是为什么我还要求对这个主题有更多的总体更好的理解。我了解它是异步的,我了解它是什么。

我的尝试

我尝试让服务返回 Observable 而不是设置自己的状态,但是当我实现一个函数来检索特定产品时,我必须再次进行 HTTP 调用,而不是使用内部状态。

非常感谢您的宝贵时间!

【问题讨论】:

  • 您能告诉我们您在哪里导入服务吗?如果服务是@Injectable (providedIn: Root),则必须确保在组件中使用服务之前注入服务,而不是与上面的代码同时注入。
  • 是的,它提供在:根目录中。我如何确保在其他组件调用它之前注入它? @Lievno

标签: angular rxjs angular2-observables


【解决方案1】:

如果您想在返回响应时收到通知,则应该从服务中返回 observable。

如果您想避免在应用程序的生命周期内发出另一个 http 请求,您可以在第一个响应上缓存响应。

import { of } from 'rxjs';
import { tap } from 'rxjs/operators';

export class GetProductsService {

  private cache = {
    allProducts;
  };

  constructor(private http: HttpClient) { }

  getAllProducts() {
    if (this.cache.allProducts) {
      return of(this.cache.allProducts);
    }

    return this.http.get('https://jsonplaceholder.typicode.com/posts').pipe(
      tap(response => this.cache.allProducts = response)
    );
  }
}

然后你应该在你的组件中订阅这个函数。

ngOnInit(): void {
  this.productsService.getAllProducts().subscribe(products => {
    this.allProducts = products;
  });
}

注意 - 这是一种非常简单的状态管理形式,我已经演示了这种模式,以帮助您学习如何使用 observables。有更多高级的状态管理模式和工具,你会及时发现。

【讨论】:

  • 啊,是的,我喜欢那个缓存部分!因为我将有一个 getSpecificProduct() 方法,这就是我不想为每个方法都进行 HTTP 调用的原因。你能简单解释一下 .pipe 和 tap 在这里做什么吗?我会再研究一下。感谢您的回复!
  • Pipe 允许您在原始 observable 和订阅者之间执行一系列操作。根据我的经验,最常见的操作是maptapswitchMap。这里tap 允许您对可观察的结果(副作用)做一些事情并隐式地将其传递给下一个管道操作员/订阅者。
  • 所有 rxjs 操作符都可以在这里找到:rxjs.dev/api(第二部分)。如您所见,有很多。
  • 啊哈,所以你可以说管道在订阅者得到它之前就像一个中间件? Tap 允许您在订阅者获取数据之前,在中间件中利用来自观察者的数据,并对其进行处理?
  • 没错。 tap 曾经被称为 do - 就像我想象的“做某事”一样。
【解决方案2】:

我使用这样的 API 服务:

@Injectable()
export class ApiService {
  constructor(
    private http: HttpClient
  ) { }

  private setHeaders(): HttpHeaders {
    const headersConfig = {
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    };

//...

    return new HttpHeaders(headersConfig);
  }

  private formatErrors(errorResponse: any) {
    return Observable.throw(errorResponse.error);
  }

  get(path: string, params: any = {}): Observable<any> {
    return this.http.get(`${environment.api_url}${path}`, { headers: this.setHeaders(), params: params })
    .catch(this.formatErrors);
  }
}

并在我的 ItemService 中像这样使用它:

 query(itemSlug: string): Observable<{ items: Item[] }> {   
    return this.apiService
      .get('/items/' + itemSlug);
  }

在我的控制器中,我像这样使用 ItemService:

this.itemsService.query(this.item.slug).subscribe(data => {
  this.items = data.items;
});

有关此方法的更多信息,请参阅https://github.com/gothinkster/angular-realworld-example-app

【讨论】:

  • 看起来很有趣!感谢您抽出宝贵时间回复! :)