Angular 2可观察对象订阅两次会执行两次调用

31

问题
我订阅了一个httpClient.get的可观察对象两次,但这意味着我的调用会执行两次。为什么会这样?

证明
每次我执行subscribe(),在登录页面上都会出现另一行。

代码(来自登录页面按钮的onSubmit())

var httpHeaders = new HttpHeaders()
  .append("Authorization", 'Basic ' + btoa(this.username + ':' + this.password));

var observable = this.httpClient.get('api/version/secured', { headers: httpHeaders});
observable.subscribe(
  () => {
    console.log('First request completed');
  },
  (error: HttpErrorResponse) => {
    console.log('First request error');
  }
);
observable.subscribe(
  () => {
    console.log('Second request completed');
  },
  (error: HttpErrorResponse) => {
    console.log('Second request error');
  }
);

控制台

zone.js:2935 GET http://localhost:4200/api/version/secured 401 (Unauthorized)
login.component.ts:54 First request error
zone.js:2935 GET http://localhost:4200/api/version/secured 401 (Unauthorized)
login.component.ts:62 First request error

无关背景
我有一个处理所有登录功能的LogonService对象。它包含一个布尔变量,用于显示我是否已登录。每当我调用登录函数时,它会订阅httpClient.get的observable,以将登录变量设置为true或false。但是登录函数还返回observable,这个observable也被订阅了。花了我一些时间才将双重请求与双重订阅联系起来。如果有比通过变量跟踪登录更好的方法,请告诉我!我正在尝试学习angular :)


4
这就是普通可观察对象的工作方式。如果你希望在代码执行一次时通知多个订阅者,你需要使用一个主题(Subject)。http://reactivex.io/rxjs/manual/overview.html#subject - David
4个回答

44

你可以在从 HttpClient.get 得到的结果上使用 share 操作符,像这样:

var observable = this.httpClient.get('api/version/secured', { headers: httpHeaders })
  .pipe(share());
你需要在脚本的顶部添加以下导入语句:
import { share } from 'rxjs/operators';

share操作符会将一个Observable设置为hot,即可在订阅者之间共享。但这只是它的一部分,我建议阅读这篇文章深入了解(你当然也可以通过谷歌搜索hot vs cold observables来获取更多信息)。


1
问题已解决,谢谢! - Rico

9
您的可观察对象是“cold”类型的:
当观察者订阅可观察对象时,其通知的生产者才会创建。例如,计时器可观察对象是“cold”类型的;每次订阅都会创建一个新的计时器。
您需要将您的可观察对象进行“multicast”,换句话说,使其成为“hot”类型的:
如果可观察对象的通知生产者不是在每次观察者订阅可观察对象时创建的,则该可观察对象是“hot”类型的。例如,使用fromEvent创建的可观察对象是“hot”类型的;生成事件的元素存在于DOM中,即当观察者订阅时不会创建该元素。
为此,您可以使用share运算符,但它仍无法保证只进行一次http调用。Share将“multicast”您的可观察对象,使其在订阅者之间共享,但一旦http调用完成,它将为新的订阅者进行新的http调用。
如果您想要缓存行为(仅在第一次调用时执行并在每次订阅时提供值给每个订阅者),您应该使用publishReplay().refCount()

进一步阅读:

发布和共享操作符


谢谢您提到缓存行为。这实际上是我自己想问的问题,但我想在这里询问之前进行更多的研究! - Rico

2
除了上面的答案,您还可以将http服务分配给可观察对象,然后订阅获取数据。例如:
export class App implements OnInit {

lessons$: Observable<Lessons[]>;

constructor(private lessonsService: lessonsService) {

}

ngOnInit() {
    this.lessons$ = this.lessonsService.loadLessons().publishLast().refCount();

    this.lessons$.subscribe(
         () => console.log('lessons loaded'),
         console.error
         );
    }
}

The Angular documentation.


-2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接