Angular - 在基本的 HTTP POST 请求和承诺中,可观察对象对我有好处吗?

Posted

技术标签:

【中文标题】Angular - 在基本的 HTTP POST 请求和承诺中,可观察对象对我有好处吗?【英文标题】:Angular - would observables benefit me in a basic HTTP POST request vs promises? 【发布时间】:2020-09-27 07:35:27 【问题描述】:

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

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

我的代码调用后端 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>

【问题讨论】:

【参考方案1】:

RxJS 提供了许多operators 和方法来修改和控制来自可观察源的数据流。更详细的列表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 =>  
);

这些是一些最普通的优势。当需要处理多个相互依赖的 observable 时,RxJS 会更有用。


在您的情况下,您可以通过管道输入 map 运算符以从响应中返回 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 可观察对象打开。

但是,如果您在多个组件中处理多个可观察对象,这两种方法都会很快变得乏味。 here 有一个巧妙的解决方法。

首先创建如下导出函数

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

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)中的管道传递到源observable。

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 一起使用时,它确保在组件被销毁时订阅总是关闭,而控制器中没有任何修改。

控制器

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 类型后缀美元符号的通用约定命名。

【讨论】:

异步管道怎么样? @user1261710:忽略了它。我已经更新了答案。

以上是关于Angular - 在基本的 HTTP POST 请求和承诺中,可观察对象对我有好处吗?的主要内容,如果未能解决你的问题,请参考以下文章

Angular 的 $http.post 在 MS Edge 中不起作用

Angular $http 正在发送 OPTIONS 而不是 PUT(不是 POST)

angular2 http.post 方法抛出 typeerror 异常

使用 Angular.js 的 HTTP POST

在 Angular 2 http.post 上添加任何选项或标题会发送 OPTIONS

如何在 Angular 4 Universal 中发出 http post 请求?