网站搜索

typescript - 在 Angular 的 ts 文件中使用 Async 管道


当我们在 HTML 中使用 async 管道时,我们使用语法 "Products$| async as products"。

如果我想在 ts 文件中使用相同的产品怎么办?这可以做到吗?

答案#1

简单来说,当对象 Products$是可观察数组时,我们可以使用 async 管道,如果你想在 ts 文件中使用,你可以订阅并像下面这样获取数据

Products$.subscribe((products) => {
   console.log(Products);
});

答案#2

经过广泛探索,我已经找到了答案,并且希望它能解决你所有的疑问。

对于你的问题没有简明的答案,但我会尽力解释一切。如果你在理解上遇到任何困难,可以随时查找更多信息,然后再问我。

让我们把它看作一个 product.service 文件,它可以包含一个数组,该数组可以是来自 API 端点的 Observable<T>、Subscribable<T> 或 Promise<T>。我们的目标是解析它并在我们的 '.ts' 文件中使用 async pipe 获取其值,这样我们每次就不用手动取消订阅了。

为了明确起见,我也提到了值的类型注解。

这是我们的 product.service.ts 文件,它作为 Angular 依赖注入系统的一部分。因此我们可以在整个应用程序中使用它们。

import { Injectable } from '@angular/core';
import { Observable, shareReplay } from 'rxjs';

import { ProductsList } from '../products.interface';
import { HttpClient } from '@angular/common/http';

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

  constructor(private http: HttpClient) {}

  // You can also use this.
  // getProducts$: Observable<ProductsList[]> = this.http.get<ProductsList[]>('/api/rooms'). 
  getRooms$: Observable<ProductsList[]> = this.http.get<ProductsList[]>('/api/products').pipe(shareReplay(1));
}

我尝试使用 AsyncPipe 实现一些逻辑,但它返回了一个空值。这种方法不推荐使用,我很快会解释原因;从

import { AsyncPipe } from '@angular/common';

这是我们在 app.component.ts 组件中的实现。

import {
  OnInit,
  OnDestroy,
  AfterViewInit,
} from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

import { ProductsList } from './products/products.interface';
import { ProductsService } from './products/services/products.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
  products$!: ProductsList[] | null;

  private destroy$: Subject<void> = new Subject<void>();

  constructor(
    private productsService: ProductsService,
  ) {
    this.productsService.getProducts$
      .pipe(takeUntil(this.destroy$))
      .subscribe((products: productsList[]) => {
        this.products$ = products;

        console.log(this.products$); // Correct Value
      });

    // console.log(this.products$); // Undefined Value
  }

  anyOtherFunction(): void {
    // console.log(this.products$); // Correct Value
  }

  ngOnInit(): void {
    // console.log(this.products$); // Undefined Value
  }

  ngAfterViewInit(): void {
    // console.log(this.products$); // Undefined Value
  } 

  ngOnDestroy(): void {
    // Automatically unsubscribe when the component is destroyed
    this.destroy$.next();
    this.destroy$.complete();
  }
}

在这里你可以看到,this.products$ 在每个生命周期钩子中都是未定义的,因为生命周期钩子在 Angular 中是同步调用的,而 this.products$ 是异步的。因此,要在任何生命周期中获取 this.products$ 的值,我们必须再次订阅它;例如

this.productsService.getProducts$
  .pipe(takeUntil(this.destroy$))
   .subscribe((products: productsList[]) => {
      this.products$ = products;

      console.log(this.products$); // Correct Value
});

在这种情况下,我们选择不使用 Angular 的 AsyncPipe,而是手动管理订阅。这意味着我们还需要处理手动取消订阅。

然而,有一个关键的区别。我们不再需要手动取消订阅;当组件被销毁时,它现在会自动发生。我们可以根据需要添加多个订阅者,并且它们将会自动取消订阅。

别忘了添加 ngOnDestroy,请参阅 app.component.ts 文件

ngOnDestroy(): void {
  // Automatically unsubscribe when the component is destroyed
  this.destroy$.next();
  this.destroy$.complete();
}
  • 我们通过使用一个 destroy$ 主题来管理组件的生命周期,在组件销毁时自动处理订阅清理。

  • 在 ngOnInit 生命周期钩子中,我们使用 takeUntil 订阅 getProducts$ 可观察对象。这个订阅被设计为在 destroy$ subject 发出时自动取消订阅。

  • 当数据到达时,它会存储在 products 属性中,使其可以在组件的逻辑和模板中随时使用。但在生命周期内例外。

  • 当组件被销毁时,ngOnDestroy 钩子会完成 destroy$ 主题,确保任何待处理的订阅都被正确处理。

这种方法提供了与异步相似的订阅管理水平,尽管需要一些手动配置。尽管如此,它有效地防止了内存泄漏,并保证组件销毁时正确清理订阅。

我们可以有多个可观察对象进行订阅

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Observable, Subject, merge } from 'rxjs';
import { ProductsList } from './products/products.interface';
import { ProductsService } from './products/services/products.service';
import { takeUntil } from 'rxjs/operators';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit, OnDestroy {
  products: ProductsList[] | null = null;
  otherData: any; // You can have multiple data properties

  private destroy$: Subject<void> = new Subject<void>();

  constructor(private productsService: ProductsService, private otherService: OtherService) {}

  ngOnInit(): void {
    // Combine multiple observables using merge
    const products$ = this.productsService.getProducts$.pipe(takeUntil(this.destroy$));
    const otherData$ = this.otherService.getOtherData$.pipe(takeUntil(this.destroy$));

    // Merge the observables into a single subscription
    const combined$ = merge(products$, otherData$);

    // Subscribe to the combined observable
    combined$.subscribe((data) => {
      if (data instanceof ProductsList) {
        this.products = data;
      } else {
        this.otherData = data;
      }
    });
  }

  ngOnDestroy(): void {
    // Automatically unsubscribe when the component is destroyed
    this.destroy$.next();
    this.destroy$.complete();
  }
}

我们使用 merge 操作符将多个可观察对象(products$和 otherData$)合并为一个可观察对象(combined$)。然后,我们订阅 combined$并根据需要处理不同类型的数据。

OR 你也可以采用 Angular 提供的推荐方式使用 Async Pipe;在 HTML 文件中,你可以使用 async pipe 来处理异步数据绑定。

<ng-container *ngIf="products$ | async as products"> {...} </ng-container>

如果你像我一样,想尝试不同的东西,那么你可以使用第一种方法