I am having one stream which carries the data from the API response and has another stream which emits the values which needs to be filtered from the source stream.
@Injectable({
providedIn: 'root'
})
export class SmpService {
private _smp$ = new ReplaySubject(1);
private _deleteSubject = new BehaviorSubject(null);
constructor(private http: HttpClient) {
const allSmp$ = this.loadSmp().pipe(map(list => list.map(item => item.id)));
const delete$ = this._deleteSubject.pipe(map(value => this.filterSmp(value)));
allSmp$
.pipe(
combineLatest(delete$, (notifications, xf) => {
return xf(notifications);
}),
tap(x => console.log('console ', x))
)
.subscribe(this._smp$);
}
loadSmp(): Observable<any> {
const contextPath = 'some_url';
const url = this.uriPrefix + contextPath;
return this.http.get(url).pipe(map((response: any) => response.notifications || []));
}
filterSmp(value) {
return notifications => notifications.filter(notification => value !== notification);
}
deleteSmp(subscribeItem) {
this._deleteSubject.next(subscribeItem);
}
getSmp(): Observable<any> {
return this._smp$.asObservable();
}
}
Filtering is working fine. But on page load, I am not able to get the initial API response rendered on the page. I only receive that when I trigger the deleteStream via some action.
Is there any way I can get the initial data even though deleteStream is not triggered?
Instead of Subject
use BehaviorSubject
with some default value. For example null
.
private _deleteSubject = new BehaviorSubject(null);
BehaviorSubject
will emit default value every time it's subscribed.
The main problem that you're running into is probably that $delete
is a "cold" observable until _deleteSubject
has a value. If you want _smp$
to be populated before _deleteSubject
is given any values then you will need to initialize it with a default value, as mentioned by @m1ch4ls.
@Injectable({
providedIn: 'root'
})
export class SmpService {
private _smp$ = new ReplaySubject(1);
private _deleteSubject = new BehaviorSubject(null);
constructor(private http: HttpClient) {
const allSmp$ = this.loadSmp().pipe(map(list => list.map(item => item.id)));
const delete$ = this._deleteSubject.pipe(map(value => value ? this.filterSmp(value) : (notifications => notifications)));
delete$
.pipe(
withLatestFrom(allSmp$, (xf, notifications) => {
return xf(notifications);
}),
tap(x => console.log('console ', x))
)
.subscribe(this._smp$);
}
loadSmp(): Observable<any> {
const contextPath = 'some_url';
const url = this.uriPrefix + contextPath;
return this.http.get(url).pipe(map((response: any) => response.notifications || []));
}
filterSmp(value) {
return (notifications) => notifications.filter(notification => value !== notification);
}
deleteSmp(subscribeItem) {
this._deleteSubject.next(subscribeItem);
}
}
Another approach of filtering one Observable using another can be
private _smp$ = new ReplaySubject(1);
private _deleteSubject = new Subject();
constructor(private http: HttpClient) {
const allSmp$ = this.loadSmp().pipe(map(list => list.map(item => item.id)));
const delete$ = this._deleteSubject.pipe(
map(value => (value ? this.filterSmp(value) : notifications => notifications))
);
allSmp$.merge(delete$)
.pipe(
startWith([]),
scan((acc, value) => {
if (typeof value === 'function') {
return [...value(acc)];
}
return [...acc, ...value];
})
)
.subscribe(this._smp$);
}
In this, we have done following :
BehaviourSubject(null) -> Subject()
combineLatest -> merge
startWith
and scan
to keep the persistent internal state And voila, it works. But still looking for other better solutions.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.