Angular - Observables 在基本的 HTTP POST 请求和承诺中对我有好处吗?

Angular - would observables benefit me in a basic HTTP POST request vs promises?

我正在使用 Angular 8 编写应用程序。我对来自 AngularJS 的框架有点陌生。我对 Promises 有很多经验。

我有 HTTP 请求到我的后端 API。这些只是 GET & POST 调用,因为我们的 Delphi 后端中没有任何 WebSocket。我已经成功调出后端 API 并使用了 promises.

我的代码调用后端 API 来获取我想向用户显示的字符串值。

我想知道如何重构我的代码以使用 Observables,这样做是否有任何好处?

服务

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { ILicenseInfo } from './ilicense-info';

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

    constructor(private http: HttpClient) { }

    public getVersionNumber() {

        const endpoint: string = 'myEndpoint';

        const params = {
            password: 'yes'
        };

        return this.http.post<ILicenseInfo>(endpoint, params)
            .toPromise()
            .then((response: ILicenseInfo) => {
                return response.versionNumberField;
            });

    }

}

组件

import { Component, OnInit } from '@angular/core';
import { Constants } from '../../../../app/core/constants/constants.service';
import { AboutService } from './about.service';

@Component({
    selector: 'ns-about',
    templateUrl: './about.component.html',
    styleUrls: ['./about.component.css']
})
export class AboutComponent implements OnInit {

    public version: string;

    constructor(private aboutService: AboutService) { }

    public ngOnInit(): void {

        this.aboutService.getVersionNumber().then((response: string) => {
            this.version = response;
        });

    }

}

模板

<FlexboxLayout>

    <FlexboxLayout>
        <Label text="Version:"></Label>
        <Label text="{{version}}"></Label>
    </FlexboxLayout>

</FlexboxLayout>

RxJS 提供了许多 operators and methods to modify and control the data flow from the source obsevable. More detailed list here.

例如

合并两个请求

forkJoin([this.http.get('url'), this.http.post('url', '')]).subscribe
  resposne => {
    // resonse[0] - GET request response
    // resonse[1] - POST request response
  },
  error => {
  }
);

通过管道将一个调用的响应作为另一个请求的输入

this.http.get('url').pipe(
  switchMap(getResponse => {
    return this.http.post('url', getResponse)
  }
).subscribe(
  response => { },
  error => { }
);

这些是一些最平凡的优势。当需要处理多个相互依赖的可观察对象时,RxJS 会更有用。


在您的情况下,您可以将 map 运算符通过管道传输到响应中的 return versionNumberField 属性。

return this.http.post<ILicenseInfo>(endpoint, params).pipe(
  map(response: ILicenseInfo => response.versionNumberField)
);

然后您可以订阅该组件以检索发出的值

public ngOnInit(): void {
  this.aboutService.getVersionNumber().subscribe(
    (response: string) => {
      this.version = response;
    }
  );
}

潜在的内存泄漏

对于 observables,非关闭订阅存在潜在的内存泄漏风险。 Angular HTTP 客户端有一个内置的取消订阅机制,但也可能会失败。所以最好在组件中关闭订阅(通常在 ngOnDestroy() 钩子中)。

import { Subscription } from 'rxjs';

httpSubscription: Subscription;

public ngOnInit(): void {
  this.httpSubscription = this.aboutService.getVersionNumber().subscribe(
    (response: string) => {
      this.version = response;
    }
  );
}

ngOnDestroy() {
  if (this.httpSubscription) {
    this.httpSubscription.unsubscribe();
  }
}

还有一种使用 RxJS takeUntil 运算符处理退订的优雅方法。

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

closed = new Subject<void>();

public ngOnInit(): void {
  this.httpSubscription = this.aboutService.getVersionNumber().pipe(
    takeUntil(this.closed)
  ).subscribe(
    (response: string) => {
      this.version = response;
    }
  );
}

ngOnDestroy() {
  this.closed.next();
  this.closed.complete();
}

因此,在 closed 可观察对象打开之前,订阅将处于活动状态。

但是如果您在多个组件中处理多个可观察对象,这两种方法都会很快变得乏味。 .

提供了一个聪明的解决方法

首先创建如下导出函数

// Based on https://www.npmjs.com/package/ng2-rx-componentdestroyed
// Source credit: 

import { OnDestroy } from '@angular/core';
import { ReplaySubject, Observable } from 'rxjs/ReplaySubject';

export function componentDestroyed(component: OnDestroy): Observable<void> {
  const oldNgOnDestroy = component.ngOnDestroy;
  const destroyed$ = new ReplaySubject<void>(1);
  component.ngOnDestroy = () => {
    oldNgOnDestroy.apply(component);
    destroyed$.next(undefined);
    destroyed$.complete();
  };
  return destroyed$.asObservable();
}

现在所有要做的就是导入函数,在组件中实现 ngOnDestroy 挂钩,并在 takeUntil(componentDestroyed(this) 中通过管道传输到源可观察对象。

import { takeUntil } from 'rxjs/operators';

public ngOnInit(): void {
  this.httpSubscription = this.aboutService.getVersionNumber().pipe(
    takeUntil(componentDestroyed(this))       // <-- pipe in the function here
  ).subscribe(
    (response: string) => {
      this.version = response;
    }
  );
}

ngOnDestroy() { }      // <-- should be implemented

更新:async管道

async 管道也可用于异步检索值。它适用于 observables 和 promises。当与 observables 一起使用时,它确保在组件被销毁时订阅始终关闭,而控制器中没有任何修改。

控制器

version$: Observable<any>;

public ngOnInit(): void {
  this.version$ = this.aboutService.getVersionNumber();
}

模板

<ng-container *ngIf="(version$ | async) as version">
  Version is {{ version }}
  <ng-container *ngIf="version > 5">
    It is already the latest version available.
  <ng-container>
</ng-container>

变量 version$ 的命名规则是在 Observable 类型后添加美元符号。