Angular HttpClient如何使用共享应答重复http请求
我在Angular的HttpClient上遇到了一个问题。我想知道如何使其重播共享的请求被重复。考虑下面的示例代码:Angular HttpClient如何使用共享应答重复http请求,angular,observable,Angular,Observable,我在Angular的HttpClient上遇到了一个问题。我想知道如何使其重播共享的请求被重复。考虑下面的示例代码: import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { shareReplay } from 'rxjs/operators'; /** Node */ export class Node { /** Attributes */ att
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { shareReplay } from 'rxjs/operators';
/** Node */
export class Node {
/** Attributes */
attributes$: Observable<any>;
constructor(private http: HttpClient, private href: string) {
this.attributes$ = http.get(href).pipe(shareReplay());
}
update(patches: any): Observable<any> {
const req = this.http.patch(this.href, patches);
// TODO: subscribe to request and update attributes
return req;
}
}
从'@angular/common/http'导入{HttpClient};
从“rxjs”导入{Observable};
从“rxjs/operators”导入{shareReplay};
/**节点*/
导出类节点{
/**属性*/
属性$:可观察的;
构造函数(私有http:HttpClient,私有href:string){
this.attributes$=http.get(href).pipe(shareReplay());
}
更新(补丁:任何):可观察{
const req=this.http.patch(this.href,patches);
//TODO:订阅请求并更新属性
返回请求;
}
}
我试图做的是在补丁
请求发送到资源后,使属性$
可观察通知一个新值
请注意,属性$
observable仅在有人订阅第一个http请求时才执行该请求。访问属性不会产生应有的效果。这是我想保留的一个重要特性
有没有办法做到这一点
提前感谢我最终解决了这个问题,因此我将与您分享我的解决方案。首先,我要说我使用TDD来解决这个问题。所以在这里,我将首先发布我使用的测试套件
import { HttpClient } from '@angular/common/http';
import {
HttpClientTestingModule,
HttpTestingController
} from '@angular/common/http/testing';
import { TestBed, inject } from '@angular/core/testing';
import { bufferCount } from 'rxjs/operators';
import { zip } from 'rxjs';
import { Node } from './node';
const rootHref = '/api/nodes/root';
const rootChildrenHref = '/api/nodes/root/children';
const rootResource = {
_links: {
'node-has-children': { href: rootChildrenHref }
}
};
function expectOneRequest(controller: HttpTestingController, href: string, method: string, body: any) {
// The following `expectOne()` will match the request's URL and METHOD.
// If no requests or multiple requests matched that URL
// `expectOne()` would throw.
const req = controller.expectOne({
method,
url: href
});
// Respond with mock data, causing Observable to resolve.
// Subscribe callback asserts that correct data was returned.
req.flush(body);
}
describe('CoreModule Node', () => {
let httpTestingController: HttpTestingController;
let subject: Node;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ HttpClientTestingModule ]
});
});
beforeEach(inject([ HttpClient, HttpTestingController ],
(http: HttpClient, testingController: HttpTestingController) => {
subject = new Node(http, rootHref);
httpTestingController = testingController;
}));
afterEach(() => {
// After every test, assert that there are no more pending requests.
httpTestingController.verify();
});
it('should be created', () => {
expect(subject).toBeTruthy();
});
it('#attributes$ is provided', () => {
expect(subject.attributes$).toBeTruthy();
});
it('#attributes$ is observable element', (done: DoneFn) => {
subject.attributes$.subscribe(root => {
expect(root).toBeTruthy();
done();
});
expectOneRequest(httpTestingController, rootHref, 'GET', rootResource);
});
it('#attributes$ observable is cached', (done: DoneFn) => {
zip(subject.attributes$, subject.attributes$).subscribe(([root1, root2]) => {
expect(root1).toBe(root2);
done();
});
expectOneRequest(httpTestingController, rootHref, 'GET', rootResource);
});
it('#update() affect attributes$', (done: DoneFn) => {
// the subscribe at the end of this pipe will trigger a children request
// but the update() call will trigger a resource request that in turn
// will trigger a children request. Therefore bufferCount will produce
// an array of size two.
subject.attributes$.pipe(bufferCount(2)).subscribe(collection => {
expect<number>(collection.length).toEqual(2);
});
subject.update([]).subscribe(root => {
expect(root).toBeTruthy();
done();
});
expectOneRequest(httpTestingController, rootHref, 'GET', rootResource);
expectOneRequest(httpTestingController, rootHref, 'PATCH', {});
expectOneRequest(httpTestingController, rootHref, 'GET', rootResource);
});
it('#update() return observable', (done: DoneFn) => {
subject.update([]).subscribe(root => {
expect(root).toBeTruthy();
done();
});
expectOneRequest(httpTestingController, rootHref, 'PATCH', {});
expectOneRequest(httpTestingController, rootHref, 'GET', rootResource);
});
});
从'@angular/common/http'导入{HttpClient};
进口{
HttpClientTestingModule,
HttpTestingController
}来自“@angular/common/http/testing”;
从'@angular/core/testing'导入{TestBed,inject};
从“rxjs/operators”导入{bufferCount};
从“rxjs”导入{zip};
从“./Node”导入{Node};
const rootHref='/api/nodes/root';
const rootChildrenHref='/api/nodes/root/childrenhref';
常量根资源={
_链接:{
“节点有子节点”:{href:rootChildrenHref}
}
};
函数expectOneRequest(控制器:HttpTestingController,href:string,方法:string,正文:any){
//以下'expectOne()'将匹配请求的URL和方法。
//如果没有请求或多个请求与该URL匹配
//'expectOne()'会扔。
const req=controller.expectOne({
方法,,
url:href
});
//使用模拟数据进行响应,导致可观察到的问题得到解决。
//订阅回调断言返回了正确的数据。
要求冲洗(主体);
}
描述('核心模块节点',()=>{
设httpTestingController:httpTestingController;
让主题:节点;
在每个之前(()=>{
TestBed.configureTestingModule({
导入:[HttpClientTestingModule]
});
});
每次之前(注入([HttpClient,HttpTestingController],
(http:HttpClient,testingController:HttpTestingController)=>{
主题=新节点(http,rootHref);
httpTestingController=测试控制器;
}));
之后(()=>{
//在每次测试之后,断言不再有挂起的请求。
httpTestingController.verify();
});
它('应该创建',()=>{
expect(subject.toBeTruthy();
});
它(“#提供了属性$”,()=>{
expect(subject.attributes$).toBeTruthy();
});
it(“#attributes$是可观察元素”,(完成:DoneFn)=>{
subject.attributes$.subscribe(根=>{
expect(root.toBeTruthy();
完成();
});
expectOneRequest(httpTestingController,rootHref,'GET',rootResource);
});
它(“#属性$observable已缓存”,(完成:DoneFn)=>{
zip(subject.attributes$,subject.attributes$).subscribe([root1,root2])=>{
expect(root1)、toBe(root2);
完成();
});
expectOneRequest(httpTestingController,rootHref,'GET',rootResource);
});
它(“#update()影响属性$”,(完成:DoneFn)=>{
//此管道末尾的订阅将触发子请求
//但是update()调用将触发一个资源请求,该请求反过来
//将触发子请求。因此bufferCount将生成
//大小为2的数组。
subject.attributes$.pipe(缓冲计数(2)).subscribe(集合=>{
期望值(集合长度)。toEqual(2);
});
subject.update([]).subscribe(root=>{
expect(root.toBeTruthy();
完成();
});
expectOneRequest(httpTestingController,rootHref,'GET',rootResource);
expectOneRequest(httpTestingController,rootHref,'PATCH',{});
expectOneRequest(httpTestingController,rootHref,'GET',rootResource);
});
它(“#update()返回observable”,(done:DoneFn)=>{
subject.update([]).subscribe(root=>{
expect(root.toBeTruthy();
完成();
});
expectOneRequest(httpTestingController,rootHref,'PATCH',{});
expectOneRequest(httpTestingController,rootHref,'GET',rootResource);
});
});
正如您所看到的,测试套件验证HTTP请求调用是否仅在有人按预期订阅了observable时发出。有了这个测试套件,我提出了节点类的以下实现:
import { HttpClient } from '@angular/common/http';
import { Observable, Subject } from 'rxjs';
import { mergeMap, shareReplay } from 'rxjs/operators';
/** Node
*
* A node is the representation of an artifact on the Sorbotics Platform
* Registry.
*/
export class Node {
/** Attributes */
public attributes$: Observable<any>;
private attributesSubject$: Subject<any> = new Subject();
private initialFetch = false;
constructor(private http: HttpClient, private href: string) {
// the attributes$ observable is a custom implementation that allow us to
// perform the http request on the first subscription
this.attributes$ = new Observable(subscriber => {
/* the Node resource is fetched if this is the first subscription to the
observable */
if (!this.initialFetch) {
this.initialFetch = true;
this.fetchResource()
.subscribe(resource => this.attributesSubject$.next(resource));
}
// connect this subscriber to the subject
this.attributesSubject$
.subscribe(resource => subscriber.next(resource));
});
}
/* Fetch Node resource on the Platform Registry */
private fetchResource: () => Observable<any> =
() => this.http.get(this.href)
/** Update node
*
* This method implement the update of the node attributes. Once the update
* is performed successfully the attributes$ observable will push new values
* to subscribed parties.
*
* @param patches Set of patches that describe the update.
*/
public update(patches: any): Observable<any> {
const req = this.http.patch(this.href, patches)
.pipe(mergeMap(() => this.fetchResource()), shareReplay(1));
req.subscribe(resource => this.attributesSubject$.next(resource));
return req;
}
}
从'@angular/common/http'导入{HttpClient};
从“rxjs”导入{observeable,Subject};
从“rxjs/operators”导入{mergeMap,shareReplay};
/**节点
*
*节点是Sorbotics平台上工件的表示
*登记处。
*/
导出类节点{
/**属性*/
公共属性$:可见;
私有属性Subject$:Subject=new Subject();
private initialFetch=false;
构造函数(私有http:HttpClient,私有href:string){
//attributes$observable是一个自定义实现,它允许我们
//对第一个订阅执行http请求
this.attributes$=newobservable(订阅者=>{
/*如果这是对的第一个订阅,则获取节点资源
可观察*/
如果(!this.initialFetch){
this.initialFetch=true;
this.fetchResource()
.subscribe(资源=>this.attributesubject$.next(资源));
}
//将此订阅服务器连接到主题
此.attributesSubject