我应该为不同的订阅使用多个 BehaviorSubject 吗?
Should I use multiple BehaviorSubject for different subscriptions?
我的 Angular
(v7) 项目中有一些同级组件和一个 DataService
,我按以下场景调用方法:
TicketComponent
添加票证并在 TicketListComponent
中调用 reloadTickets
方法,类似地 FileComponent
添加文件并在 FileListComponent
中调用 reloadFiles
方法通过DataService
如下图:
DatasService.ts:
export class DatasService {
private eventSubject = new BehaviorSubject<any>(undefined);
getEventSubject(): BehaviorSubject<any> {
return this.eventSubject;
}
reloadTickets(param: boolean) {
this.eventSubject.next(param);
}
reloadFiles(param: any) {
this.eventSubject.next(param);
}
}
TicketComponent:
ngOnInit(): void {
this.dataService.getEventSubject().subscribe((param: any) => {
this.reloadTickets();
});
}
文件组件:
ngOnInit(): void {
this.dataService.getEventSubject().subscribe((param: any) => {
this.reloadFiles();
});
}
当我对这两个方法使用单个 BehaviorSubject
时,当调用其中一个方法时,这两个方法会同时被调用。我的意思是 由于它们都通过 getEventSubject() 方法订阅,因此 reloadTickets() 方法也会触发 DataService 中的 reloadFiles(),因为它们都使用相同的主题 (eventSubject)。 我知道创建另一个 BehaviorSubject
和 getEventSubject
方法可以解决问题,但我很困惑是否应该对所有独立方法调用都这样做,或者是否有更聪明的方法通过使用单个 [=25 来解决问题=] 如下所述:
能否请您 post 正确使用此场景?
更新:
最后,我使用了以下方法,以便使用 单个 BehaviorSubject 在不同组件之间调用不同的方法。
EventProxyService:
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable()
export class EventProxyService {
private eventTracker = new BehaviorSubject<any>(undefined);
getEvent(): BehaviorSubject<any> {
return this.eventTracker;
}
setEvent(param: any): void {
this.eventTracker.next(param);
}
}
CommentComponent: 添加评论后调用ListComponent的方法:
import { EventProxyService } from './eventProxy.service';
export class CommentComponent implements OnInit {
constructor(private eventProxyService: EventProxyService) {}
public onSubmit() {
//...
this.reloadComment(true);
}
reloadComment(param: boolean): void {
this.eventProxyService.setEvent(param);
}
}
ListComponent: 通过 CommentComponent 中的 reloadComment() 方法触发:
import { EventProxyService } from './eventProxy.service';
export class ListComponent implements OnInit {
subscription;
constructor(private eventProxyService: EventProxyService) {}
ngOnInit() {
this.subscription = this.eventProxyService.getEvent().subscribe((param: any) => {
this.listComment(param);
});
}
// Multi value observables must manually unsubscribe to prevent memory leaks
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
listComment(param) {
//retrieve data from service
}
}
是的,有一种更聪明的方法可以动态创建 BehaviorSubject
,这里是示例。希望对您有所帮助。
1./DatasService.ts
interface Event {
key: string;
value: any;
}
@Injectable({
providedIn: 'root'
})
export class Broadcaster {
// subject
protected _eventsSubject = new BehaviorSubject<any>(undefined);
constructor() {
}
broadcast(key: any, value: any) {
this._eventsSubject.next({ key, value }); // here we are setting the key and value of our subject
}
on<T>(key: any): Observable<T> {
return this._eventsSubject.asObservable()
.pipe(
filter(e => e.key === key),
map(e => e.value)
);
}
}
2./TicketComponent
// this is a component which consume the same BehaviorSubject but we are getting a value from "ticket" key
import { Broadcaster } from '../BrodcastService.service';
export class ComponentOne implements OnInit {
constructor(private broadcaster: Broadcaster) { }
someFunction() {
//"ticket" is our key name. so we are getting a value of that key only
this.broadcaster.on('ticket').subscribe(response => {
console.log(response); // here you are getting the data from the other component
});
}
3./文件组件
// this is a component which consume the same BehaviorSubject but we are getting a value from "file" key
import { Broadcaster } from '../BrodcastService.service';
export class componentTwo implements OnInit {
constructor(private broadcaster: Broadcaster) { }
someFunction() {
//"file" is our key name. so we are getting a value of that key only
this.broadcaster.on('file').subscribe(response => {
console.log(response); // here you are getting the data from the other component
});
}
所以如果你想发送票据组件的数据
发送票据组件数据的组件
import { Broadcaster } from '../BrodcastService.service';
export class ComponentOne implements OnInit {
constructor(private broadcaster: Broadcaster) { }
someFunction() {
this.broadcaster.broadcast('ticket', 'data for ticket');
}
为文件组件发送数据的组件
import { Broadcaster } from '../BrodcastService.service';
export class ComponentOne implements OnInit {
constructor(private broadcaster: Broadcaster) { }
someFunction() {
this.broadcaster.broadcast('file', 'data for file');
}
所以基本上我们只创建一个 BehaviorSubject
但 BehaviorSubject
包含一个存储我们数据的多个对象,我们通过使用您的情况下的密钥访问数据,我们有密钥名称file
和 ticket
。
我很难知道你到底想达到什么目的,但是..
首先,永远不要使用这个结构,因为它会创建一个无限循环:
this.dataService.getEventSubject().subscribe((param: any) => {
this.reloadTickets();
});
当值发生变化时,您可以访问组件中的新值。您应该只在操作数据后更新可观察对象,例如:
// Reads the observable
this.dataService.getEventSubject().subscribe((param: any) => {
this.populateForm();
});
// Updates the observable
this.addTicket() {
this.dataService.addTicket()
}
接下来,您应该始终输入您的变量,例如:
export interface Ticket {
artist: string;
price: number;
}
export interface File {
name: string;
type: 'gif' | 'jpg' | 'png';
}
将类型添加到 Observable 后,您会注意到实际上需要 两个主题。
// As a convention, It's recommended to use singular form, and add a $.
public ticket$ = new BehaviorSubject<Ticket[]>(null);
public file$ = new BehaviorSubject<File[]>(null);
此外,我应该将它们设置为 public,以便在不需要 get()
的情况下轻松访问。您可以通过注入服务并调用可观察对象来简单地访问它。
constructor(
private dataService: DataService
)
this.dataService.ticket$
当您需要将它们设为私有时,您应该使用:
private _ticket$: Subject<Ticket[]> = new BehaviorSubject<Ticket[]>(null);
public ticket$ = this._ticket$.asObservable();
使用该构造,您可以读取每个 service/component 中的可观察对象,但只能在包含的服务中更新它们 .
您应该始终做的另一件事是完成组件中的可观察对象,否则您将永远保持开放订阅:
private destroy$ = new Subject<any>();
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
this.dataService.ticket$.pipe(takeUntil(this.destroy$)).subscribe(tickets => {
// Do something
})
底线:当你遵循正确的模式时,你得到的会少很多issues/bugs。
我的 Angular
(v7) 项目中有一些同级组件和一个 DataService
,我按以下场景调用方法:
TicketComponent
添加票证并在 TicketListComponent
中调用 reloadTickets
方法,类似地 FileComponent
添加文件并在 FileListComponent
中调用 reloadFiles
方法通过DataService
如下图:
DatasService.ts:
export class DatasService {
private eventSubject = new BehaviorSubject<any>(undefined);
getEventSubject(): BehaviorSubject<any> {
return this.eventSubject;
}
reloadTickets(param: boolean) {
this.eventSubject.next(param);
}
reloadFiles(param: any) {
this.eventSubject.next(param);
}
}
TicketComponent:
ngOnInit(): void {
this.dataService.getEventSubject().subscribe((param: any) => {
this.reloadTickets();
});
}
文件组件:
ngOnInit(): void {
this.dataService.getEventSubject().subscribe((param: any) => {
this.reloadFiles();
});
}
当我对这两个方法使用单个 BehaviorSubject
时,当调用其中一个方法时,这两个方法会同时被调用。我的意思是 由于它们都通过 getEventSubject() 方法订阅,因此 reloadTickets() 方法也会触发 DataService 中的 reloadFiles(),因为它们都使用相同的主题 (eventSubject)。 我知道创建另一个 BehaviorSubject
和 getEventSubject
方法可以解决问题,但我很困惑是否应该对所有独立方法调用都这样做,或者是否有更聪明的方法通过使用单个 [=25 来解决问题=] 如下所述:
能否请您 post 正确使用此场景?
更新:
最后,我使用了以下方法,以便使用 单个 BehaviorSubject 在不同组件之间调用不同的方法。
EventProxyService:
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable()
export class EventProxyService {
private eventTracker = new BehaviorSubject<any>(undefined);
getEvent(): BehaviorSubject<any> {
return this.eventTracker;
}
setEvent(param: any): void {
this.eventTracker.next(param);
}
}
CommentComponent: 添加评论后调用ListComponent的方法:
import { EventProxyService } from './eventProxy.service';
export class CommentComponent implements OnInit {
constructor(private eventProxyService: EventProxyService) {}
public onSubmit() {
//...
this.reloadComment(true);
}
reloadComment(param: boolean): void {
this.eventProxyService.setEvent(param);
}
}
ListComponent: 通过 CommentComponent 中的 reloadComment() 方法触发:
import { EventProxyService } from './eventProxy.service';
export class ListComponent implements OnInit {
subscription;
constructor(private eventProxyService: EventProxyService) {}
ngOnInit() {
this.subscription = this.eventProxyService.getEvent().subscribe((param: any) => {
this.listComment(param);
});
}
// Multi value observables must manually unsubscribe to prevent memory leaks
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
listComment(param) {
//retrieve data from service
}
}
是的,有一种更聪明的方法可以动态创建 BehaviorSubject
,这里是示例。希望对您有所帮助。
1./DatasService.ts
interface Event {
key: string;
value: any;
}
@Injectable({
providedIn: 'root'
})
export class Broadcaster {
// subject
protected _eventsSubject = new BehaviorSubject<any>(undefined);
constructor() {
}
broadcast(key: any, value: any) {
this._eventsSubject.next({ key, value }); // here we are setting the key and value of our subject
}
on<T>(key: any): Observable<T> {
return this._eventsSubject.asObservable()
.pipe(
filter(e => e.key === key),
map(e => e.value)
);
}
}
2./TicketComponent
// this is a component which consume the same BehaviorSubject but we are getting a value from "ticket" key
import { Broadcaster } from '../BrodcastService.service';
export class ComponentOne implements OnInit {
constructor(private broadcaster: Broadcaster) { }
someFunction() {
//"ticket" is our key name. so we are getting a value of that key only
this.broadcaster.on('ticket').subscribe(response => {
console.log(response); // here you are getting the data from the other component
});
}
3./文件组件
// this is a component which consume the same BehaviorSubject but we are getting a value from "file" key
import { Broadcaster } from '../BrodcastService.service';
export class componentTwo implements OnInit {
constructor(private broadcaster: Broadcaster) { }
someFunction() {
//"file" is our key name. so we are getting a value of that key only
this.broadcaster.on('file').subscribe(response => {
console.log(response); // here you are getting the data from the other component
});
}
所以如果你想发送票据组件的数据 发送票据组件数据的组件
import { Broadcaster } from '../BrodcastService.service';
export class ComponentOne implements OnInit {
constructor(private broadcaster: Broadcaster) { }
someFunction() {
this.broadcaster.broadcast('ticket', 'data for ticket');
}
为文件组件发送数据的组件
import { Broadcaster } from '../BrodcastService.service';
export class ComponentOne implements OnInit {
constructor(private broadcaster: Broadcaster) { }
someFunction() {
this.broadcaster.broadcast('file', 'data for file');
}
所以基本上我们只创建一个 BehaviorSubject
但 BehaviorSubject
包含一个存储我们数据的多个对象,我们通过使用您的情况下的密钥访问数据,我们有密钥名称file
和 ticket
。
我很难知道你到底想达到什么目的,但是..
首先,永远不要使用这个结构,因为它会创建一个无限循环:
this.dataService.getEventSubject().subscribe((param: any) => {
this.reloadTickets();
});
当值发生变化时,您可以访问组件中的新值。您应该只在操作数据后更新可观察对象,例如:
// Reads the observable
this.dataService.getEventSubject().subscribe((param: any) => {
this.populateForm();
});
// Updates the observable
this.addTicket() {
this.dataService.addTicket()
}
接下来,您应该始终输入您的变量,例如:
export interface Ticket {
artist: string;
price: number;
}
export interface File {
name: string;
type: 'gif' | 'jpg' | 'png';
}
将类型添加到 Observable 后,您会注意到实际上需要 两个主题。
// As a convention, It's recommended to use singular form, and add a $.
public ticket$ = new BehaviorSubject<Ticket[]>(null);
public file$ = new BehaviorSubject<File[]>(null);
此外,我应该将它们设置为 public,以便在不需要 get()
的情况下轻松访问。您可以通过注入服务并调用可观察对象来简单地访问它。
constructor(
private dataService: DataService
)
this.dataService.ticket$
当您需要将它们设为私有时,您应该使用:
private _ticket$: Subject<Ticket[]> = new BehaviorSubject<Ticket[]>(null);
public ticket$ = this._ticket$.asObservable();
使用该构造,您可以读取每个 service/component 中的可观察对象,但只能在包含的服务中更新它们 .
您应该始终做的另一件事是完成组件中的可观察对象,否则您将永远保持开放订阅:
private destroy$ = new Subject<any>();
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
this.dataService.ticket$.pipe(takeUntil(this.destroy$)).subscribe(tickets => {
// Do something
})
底线:当你遵循正确的模式时,你得到的会少很多issues/bugs。