Just as the title says, in Angular 2, is there any way to check if source is already subscribed? Because I have to check it before using
this.subscription.unsubscribe();
This is my code:
this.Source = Rx.Observable.timer(startTime, 60000).timeInterval().pluck('interval');
this.Subscription = this.Source
.subscribe(data => { something }
and then I want to be sure that it is subscribed before calling unsubscribe()
It seems you can check whether the subscription is closed with
this.subscription.closed
indicate whether this Subscription has already been unsubscribed.
I had a similar case, I had a if condition with one optional subscribe:
if (languageIsSet) {
// do things...
} else {
this.langSub = this.langService.subscribe(lang => {
// do things...
});
}
If you want to call unsubscribe safely (you don't know if is subscribed or not), just initialize the subscription with EMPTY instance:
private langSub: Subscription = Subscription.EMPTY;
Now you can unsubscribe without errors.
You can check if Subject has observers because it has a public property observers.
With Observables you can't because they don't typically have arrays of observers. Only if you've multicasted them via a Subject with the multicast() operator for example.
Maybe if you could describe your use case in more detail I'll be able to give you better advice.
const source = ...;
let subscribed = false;
Rx.Observable.defer(() => {
subscribed = true;
return source.finally(() => { subscribed = false });
})
As I can see in your code you always create a subscription.
So if you created subscriptions object it means subscription exists and you can unsubscribe.
It Still a bit not clear why you need to check is any subsection exist
Btw. unsubscribe() method checking is subscription closed or not.
Subscription is closed if somebody called unsubscribe() or observable is compleated
Related
I read this implementation of an event emitter on LeetCode and wanted to ask a few questions.
What purpose does it serve to have the release method in the return of the subscribe method? Why can't I make it its own method?
How do I use the unsubscribe method like this and how would I use it if it was its own method?
The author said the reason behind putting the callback inside an object was to be able to add multiple callbacks of the same name. Is that considered good practice?
Any recommendations as to how to make this implementation better (readability, structure)?
Why isn't subscriptions variable defined in a constructor?
Thank you.
class EventEmitter {
subscriptions = new Map()
subscribe(eventName, callback) {
if (!this.subscriptions.has(eventName)) {
this.subscriptions.set(eventName, new Set())
}
const newSub = { callback }
this.subscriptions.get(eventName).add(newSub)
return {
unsubscribe: () => {
const evSub = this.subscriptions.get(eventName)
evSub.delete(newSub)
if (evSub.size === 0)
this.subscriptions.delete(eventName)
}
}
}
emit(eventName, ...args) {
const callbacks = this.subscriptions.get(eventName)
if (!callbacks) return
for (let c of callbacks) {
c.callback(...args)
}
}
}
What purpose does it serve to have the release method in the return of the subscribe method? Why can't I make it its own method?
Answer 1: In case unsubscribing needs information only available from making the subscription (like a subscription eventName) it makes sense to provide the function directly from the creation. It saves you having to store data needed in the unsubscription process in some intermediary form. You can make it is own method if you want, but still need to return it as in this code:
unsubscribe(eventName) => {
const evSub = this.subscriptions.get(eventName)
evSub.delete(newSub)
if (evSub.size === 0)
this.subscriptions.delete(eventName)
}
subscribe(eventName, callback) {
if (!this.subscriptions.has(eventName)) {
this.subscriptions.set(eventName, new Set())
}
const newSub = { callback }
this.subscriptions.get(eventName).add(newSub)
return () => unsubscribe(eventName);
}
How do I use the unsubscribe method like this and how would I use it if it was its own method?
Answer 2: You store the return value from subscribe some place you can access it, then invoke it if needed by calling unsubscribe(). How it's used is not different whether it's in its own function or not. It still needs to know the eventName so you need the one returned from subscribe. Like in this code:
const unsub = subscribe("event1", () => {});
// then later
unsub(); // unsubscribe from event1
The author said the reason behind putting the callback inside an object was to be able to add multiple callbacks of the same name. Is that considered good practice?
Answer 3: There's nothing wrong with it. It's a design choice.
Any recommendations as to how to make this implementation better (readability, structure)?
Answer 4: That's personal choice.
Why isn't subscription defined in a constructor?
Answer 5: I assume you're asking about subscriptions variable (plural) and not the subscription function. It's defined as a class variable which doesn't need any initialization specific to a ctor parameter so there's no need to make one. You could put it in a ctor if you wanted to but it just makes the code longer without any real benefit. If the ctor took in some parameters that would affect the initial value of subscriptions then it could be done in the ctor.
I am using Angular 5 and have subscribed an observable using the subscribe() method. I want to know if only calling the unsubscribe() method on the subscription will be sufficient to cleanup everything, or should I also call remove() method?
code snippet:
`
// somewhere in a method
this.s1 = someObservable.subscribe((value) => {
//somecode
});
// in ngOnDestroy
this.s1.unsubscribe(); // should I also call .remove()
`
.remove remove the subscription from an internal list, but it does not unsubscribe.
.unsubscribe clean up everything, do the unsubscribe and remove the observer from the internal list. (There was a bug (fixed) that didn't remove the observer from the list)
.takeWhile keep alive the subscription whilst a certain situation continues to be false
example:
this.service.method()
.subscribe(res => {
//logic
});
this will never unsubscribe.
this.service.method()
takeWhile(() => this.isAlive) // <-- custom variable setted to true
.subscribe(res => {
//logic
});
ngOnDestroy(){
this.isAlive = false;
}
Automatic unsubscribe when the component is going to be destroyed.
this.s1 = someObservable.subscribe((value) => {
//somecode
});
public yourMethod(){
this.s1.unsubscribe();
}
this subscription will exists and be "alive" until yourFunction is not called.
--
I personally like to use the rxjs operator takeWhile to keep the code clean. In a very big project or single component having multiple subscription it's confusing having (IE) 30 variables: Subscription. So If you are asking when to use the takeWhile operator my answer is: (Taking as example one subscription) -> If you are sure that the unsubscribe needs to be done when the component is destroyed, use takeWhile. If you need to unsubscribe in a certain scenario where the component is still "alive", use the second example I wrote.
Hope to have clarified the argument.
I have been using these two (pretty identical methods) of calling throttleTime, but I am unsure if thery are actually identical or not.
this.scrollSubject$ = new Subject<Event>();
this.scrollSubscription$ = this.scrollSubject$
.asObservable()
.throttleTime(400, undefined, { trailing: true })
.subscribe(event => this.throttledScrollHandler(event));
The same as above, but with asObservable
this.scrollSubscription$ = this.scrollSubject$
.throttleTime(400, undefined, { trailing: true})
.subscribe( event => this.throttledScrollHandler(event));
I forgot to add the asObservable on my second one, but it behaved in the same way, which was a surprise. Are these two methods the same?
I'm using Observables in Angular, but I don't think this is Angular specific
The answer to the question in the title is: yes, if you want to ensure that the subject's next, error and complete methods cannot be called via the composed observable, you should use asObservable.
So the two snippets in the question are not the same.
Subject implements lift. This means that the observable returned from an operator is a Subject. So, unless asObservable is called, it will be possible for next, error or complete to be called on the composed observable.
const subject = new Rx.Subject();
const withoutAsObservable = subject
.do(value => console.log(value));
console.log("withoutAsObservable is a subject:", withoutAsObservable instanceof Rx.Subject);
const withAsObservable = subject
.asObservable()
.do(value => console.log(value));
console.log("withAsObservable is a subject:", withAsObservable instanceof Rx.Subject);
subject.subscribe(
value => console.log("subject value:", value),
error => console.log("subject error:", error)
);
withoutAsObservable.next("some value");
withoutAsObservable.error(new Error("some error"));
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://unpkg.com/rxjs#5/bundles/Rx.min.js"></script>
For more information on lift see this issue.
Regarding your comment on another answer:
I can call this.scrollSubscription$['next'](new Event('asdf'));, and throttledScrollHandler() will print out that event
Calling next on the Subscription calls the next method on the Subscriber that was created from the next function you passed to subscribe. It has nothing to so with the subject and is not equivalent to calling the subject's next method.
The implementation of subscribe calls toSubscriber and that subscriber is returned.
Subscriber extends Subscription, but it also has a next method. And that's what you are calling.
I have three observable sources in my code that emit values of the same type.
const setTitle$ = params$.do(
params => this.titleService.setTitle( `${params[1].appname} - ${this.pagename}` )
).switchMap(
() => Observable.of(true)
);
const openDocument$ = params$.switchMap(
params => this.openDocument(params[0].id)
);
const saveDocument$ = params$.switchMap(
params => this.saveDocument(params[0].id)
);
When i use them in race like this
setTitle$.race(
openDocument$,
saveDocument$
).subscribe();
works only setTitle and when i subscribe manually to another two sorces like
const openDocument$ = params$.switchMap(
params => this.openDocument(params[0].id)
).subscribe();
const saveDocument$ = params$.switchMap(
params => this.saveDocument(params[0].id)
).subscribe();
then they work too. Help me understand why it's going on and how to force to work all sources in race, merge, etc.
From the documentation, the .race() operator does this:
The observable to emit first is used.
That is why, you will only get ONE emission, because only one out of the three observables that emits first will get emitted.
What you are looking for is .forkJoin() or .combineLatest().
If you want all the observables to execute in parallel and wait for ALL of them to come back as one observables, use .forkJoin():
Observable
.forkJoin([...setTitle$, openDocument$, saveDocument$])
.subscribe(([setTitle, openDocument, saveDocument]) => {
//do something with your your results.
//all three observables must be completed. If any of it was not completed, the other 2 observables will wait for it
})
If you however wants to listen to every emission of all the observables regardless when they are emitted, use .combineLatest():
Observable
.combineLatest(setTitle$, openDocument$, saveDocument$)
.subscribe(([setTitle, openDocument, saveDocument]) => {
//do something with your your results.
// as long as any of the observables completed, it will be emitted here.
});
Problem was with shared params source.
const params$ = this.route.params.map(
routeParams => {
return {
id: <string>routeParams['id']
};
}
).combineLatest(
this.config.getConfig()
).share();
I have shared it with share operator. But in this article from the first comment to my question i found this:
When using multiple async pipes on streams with default values, the .share() operator might cause problems:
The share() will publish the first value of the stream on the first subscription. The first async pipe will trigger that subscription and get that initial value. The second async pipe however will subscribe after that value has already been emitted and therefore miss that value.
The solution for this problem is the .shareReplay(1) operator, which will keep track of the previous value of the stream. That way all the async pipes will get the last value.
I replaced share() with shareReplay(1) and all sources began emitting values.
const params$ = this.route.params.map(
routeParams => {
return {
id: <string>routeParams['id']
};
}
).combineLatest(
this.config.getConfig()
).shareReplay(1);
Thanks to everyone for help!
I might be off on the process, but here goes:
I have an angular2 service. The source for the data of this service is going to be localstorage... later optionally updated when a DB call using http returns. Because I'll be wanting to update the data returned as the various sources come back, it appears I want to use an observables. For now, I'm just trying to get the concept down, so I've skipped the localstorage aspect... but I'm including the 'backstory' so it makes (some) sense as to why I'm wanting to do this in multiple methods.
My thought was I would have a "getHTTPEvents()" method that would return an observable with the payload being the events from the DB. (the theory being that at some point in the future I'd also have a 'getLSEvents()' method that would piggy back in there)
To mock that up, I have this code:
private eventsUrl = 'app/mock-events.json';
getHTTPEvents() : Observable<Array<any>> {
return this._http.get(this.eventsUrl)
.map(response => response.json()['events'])
.catch(this.handleError); // handle error is a logging method
}
My goal would be to create a method that allows filtering on the returned events yet still returns an observable to users of the service. That is where my problem is. With that goal, I have a public method which will be called by users of the service. (attempted to use pattern from here https://coryrylan.com/blog/angular-2-observable-data-services)
public getEvents(key:string,value:string) : Observable<Array<any>> {
var allEventsObserve : Observable<Array<any>> = this.getHTTPEvents();
var filteredEventsObserve : Observable<Array<any>>;
allEventsObserve
.subscribe(
events => {
for(var i=0;i<events.length;i++) {
if(events[i][key]==value) {
console.log('MATCH!!!' + events[i][key]); // THIS WORKS!
return new Observable(observer => filteredEventsObserve = observer); // what do I need to return here? I want to return an observable so the service consumer can get updates
}
}
return allEventsObserve
},
error => console.error("Error retrieving all events for filtering: " + error));
}
The above doesn't work. I've watch lots of videos and read lots of tutorials about observables, but nothing I can find seems to go more indepth other than creating and using the http observable.
I further tried this method of making the new observable:
var newObs = Observable.create(function (observer) {
observer.next(events[i]);
observer.complete(events[i]);
});
And while at least that compiles, I'm not sure how to 'return' it at the right time... as I can't "Create" it outside the allEventsObserve.subscribe method (because 'events' doesn't exist) and can't (seem) to "return" it from within the subscribe. I'm also not entirely sure how I'd then "trigger" the 'next'...?
Do I need to modify the data within allEventsObserve and somehow simply still return that? Do I make a new observable (as attempted above) with the right payload - and if so, how do I trigger it? etc... I've checked here: How to declare an observable on angular2 but can't seem to follow how the 'second' observable gets triggered. Perhaps I have the entire paradigm wrong?
It appears that you're misunderstanding what an RxJS operator (like map, filter, etc) actually returns, and I think correcting that will make the solution clear.
Consider this short example:
allEventsObserve
.map(events => {
return 'this was an event';
})
Granted, it's a pretty useless example since all of the data from events is lost, but let's ignore that for now. The result of the code above is not an array of strings or anything else, it's actually another Observable. This Observable will just emit the string 'this was an event' for each array of events emitted by allEventsObserve This is what allows us to chain operators on observables -- each operator in the chain returns a new Observable that emits items that have been modified in some way be the previous operator.
allEventsObserve
.map(events => {
return 'this was an event';
})
.filter(events => typeof events !== 'undefined')
allEventsObserve is obviously an Observable, allEventsObserve.map() evaluates to an Observable, and so does allEventsObserve.map().filter().
So, since you're expecting your function to return an Observable, you don't want to call subscribe just yet, as doing so would return something that isn't really an Observable.
With that in mind, your code can be rewritten in the following way:
public getEvents(key:string,value:string) : Observable<Array<any>> {
var allEventsObserve : Observable<Array<any>> = this.getHTTPEvents();
return allEventsObserve
.map(events => {
var match = events.filter(event => event[key] == value);
if (match.length == 0) {
throw 'no matching event found';
} else {
return match[0];
}
})
.catch(e => {
console.log(e);
return e;
});
}
Since getEvents returns an Observable, somewhere else in your code you would do something like getEvents().subscribe(events => processEvents()) to interact with them. This code also assumes that this.getHTTPEvents() returns an Observable.
Also, notice that I changed your for loop to a call to filter, which operates on arrays. events in this case is a plain-old JavaScript Array, so the filter that is getting called is not the same filter as the RxJS operator filter.