DisposeBag
这个想法的灵感来自RxSwift的DisposeBag,所以我决定开发一个类似但简单的结构。
DisposeBag是一个数据结构,它包含对所有打开订阅的引用。它促进了组件中订阅的处理,同时为我们提供了api来跟踪打开订阅的状态。
优势
非常简单的API,使您的代码看起来简单和小。
提供用于跟踪开放订阅状态的API(允许您显示不确定的进度条)
没有依赖注入/包。
使用
在组件:
@Component({
selector: 'some-component',
templateUrl: './some-component.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SomeComponent implements OnInit, OnDestroy {
public bag = new DisposeBag()
constructor(private _change: ChangeDetectorRef) {
}
ngOnInit(): void {
// an observable that takes some time to finish such as an api call.
const aSimpleObservable = of(0).pipe(delay(5000))
// this identifier allows us to track the progress for this specific subscription (very useful in template)
this.bag.subscribe("submission", aSimpleObservable, () => {
this._change.markForCheck() // trigger UI change
})
}
ngOnDestroy(): void {
// never forget to add this line.
this.bag.destroy()
}
}
在模板:
<!-- will be shown as long as the submission subscription is open -->
<span *ngIf="bag.inProgress('submission')">submission in progress</span>
<!-- will be shown as long as there's an open subscription in the bag -->
<span *ngIf="bag.hasInProgress">some subscriptions are still in progress</span>
实现
import { Observable, Observer, Subject, Subscription, takeUntil } from "rxjs";
/**
* This class facilitates the disposal of the subscription in our components.
* instead of creating _unsubscribeAll and lots of boilerplates to create different variables for Subscriptions;
* you can just easily use subscribe(someStringIdentifier, observable, observer). then you can use bag.inProgress() with
* the same someStringIdentifier on you html or elsewhere to determine the state of the ongoing subscription.
*
* don't forget to add onDestroy() { this.bag.destroy() }
*
* Author: Hamidreza Vakilian (hvakilian1@gmail.com)
* @export
* @class DisposeBag
*/
export class DisposeBag {
private _unsubscribeAll: Subject<any> = new Subject<any>();
private subscriptions = new Map<string, Subscription>()
/**
* this method automatically adds takeUntil to your observable, adds it to a private map.
* this method enables inProgress to work. don't forget to add onDestroy() { this.bag.destroy() }
*
* @template T
* @param {string} id
* @param {Observable<T>} obs
* @param {Partial<Observer<T>>} observer
* @return {*} {Subscription}
* @memberof DisposeBag
*/
public subscribe<T>(id: string, obs: Observable<T>, observer: Partial<Observer<T>> | ((value: T) => void)): Subscription {
if (id.isEmpty()) {
throw new Error('disposable.subscribe is called with invalid id')
}
if (!obs) {
throw new Error('disposable.subscribe is called with an invalid observable')
}
/* handle the observer */
let subs: Subscription
if (typeof observer === 'function') {
subs = obs.pipe(takeUntil(this._unsubscribeAll)).subscribe(observer)
} else if (typeof observer === 'object') {
subs = obs.pipe(takeUntil(this._unsubscribeAll)).subscribe(observer)
} else {
throw new Error('disposable.subscribe is called with an invalid observer')
}
/* unsubscribe from the last possible subscription if in progress. */
let possibleSubs = this.subscriptions.get(id)
if (possibleSubs && !possibleSubs.closed) {
console.info(`Disposebag: a subscription with id=${id} was disposed and replaced.`)
possibleSubs.unsubscribe()
}
/* store the reference in the map */
this.subscriptions.set(id, subs)
return subs
}
/**
* Returns true if any of the registered subscriptions is in progress.
*
* @readonly
* @type {boolean}
* @memberof DisposeBag
*/
public get hasInProgress(): boolean {
return Array.from(this.subscriptions.values()).reduce(
(prev, current: Subscription) => {
return prev || !current.closed }
, false)
}
/**
* call this from your template or elsewhere to determine the state of each subscription.
*
* @param {string} id
* @return {*}
* @memberof DisposeBag
*/
public inProgress(id: string) {
let possibleSubs = this.subscriptions.get(id)
if (possibleSubs) {
return !possibleSubs.closed
} else {
return false
}
}
/**
* Never forget to call this method in your onDestroy() method of your components.
*
* @memberof DisposeBag
*/
public destroy() {
this._unsubscribeAll.next(null);
this._unsubscribeAll.complete();
}
}