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
类型后添加美元符号。
我正在使用 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
类型后添加美元符号。