I have created an observable, which is given below
private permissionSubject = new Subject<any>();
permissionObservable$ = this.permissionSubject.asObservable();
constructor( public apiService: ApiService) { }
updatePermissionsData(permissionData){
this.permissionSubject.next(permissionData);
}
getPermissions(){
return this.apiService.get('/getUserPrivileges')
.map(data => data)
}
Here what I am doing is, whenever I am getting the data, I am pushing the data to Observable
Ex: consider an observable -> [1, 2] and pushing 3 as it is new, data
now observable will become [1, 2, 3]
But I want to remove 1, 2 value from Observable before pushing 3 to it. How can I do that?
Is Observable.empty() will do that, if it can, how can I update my code?
I have seen many questions in stackoverflow, but nothing helped :-( that's why I am asking this question again...
Updated code
Subscribing observable
checkPermissions() {
this.checkPermService.permissionObservable$.subscribe(
// Below one is getting executed for so many times whenever
observable get new data (stream data)
data => {
this.saveMenuItemsOnPermissions(data)
}
)
}
I think there is a misunderstanding of how Observables work. You have no buffer/memory structure in your code.
Your Code explained
// instance of a subject.
// Subjects don't have memory!! The stream is pushed to subscribers once.
private permissionSubject = new Subject<any>();
// Here you make a restriction on `permissionObservable$`, so it listens, but doesn't publish
permissionObservable$ = this.permissionSubject.asObservable();
// constructor instanciates apiService
constructor( public apiService: ApiService) { }
// each time this function is called, permissionData is pushed through
// permissionObservable and permissionObservable$ subscribers.
updatePermissionsData(permissionData){
this.permissionSubject.next(permissionData);
}
// calls a service and waits for subscription (http call I suppose)
// the map function is useless BTW
getPermissions(){
return this.apiService.get('/getUserPrivileges')
.map(data => data)
}
Observable.empty()
create an Observable that emits no items but terminates normally
Observable.empty() is not a method !! It is an observable whose purpose is to :
emit nothing
hang the stream
Edit:
If you just want to ignore the 2 first elements of an observable, you can use skip operator.
Skip operator:
Skip allows you to ignore the first x emissions from the source.
Generally skip is used when you have an observable that always emits
certain values on subscription that you wish to ignore. Perhaps those
first few aren't needed or you are subscribing to a Replay or
BehaviorSubject and do not need to act on the initial values. Reach
for skip if you are only concerned about later emissions.
// Below one is getting executed for so many times whenever observable get new data (stream data)
checkPermissions() {
this.checkPermService.permissionObservable$.skip(2)
.subscribe( data => {
this.saveMenuItemsOnPermissions(data)
})
}
There are 2 important points to bear in mind:
Subscription must occur before observable starts emitting
checkPermissions will ignore the 2 first received elements during subscription, but it will take all the following others.
Related
I want to use switchMap in my subscriptions array, I want to call invokeRequest method which triggers http requests, basically I want to cancel subscription if same http call is triggered, can anyone please help.
private subscriptions: Subscription[] = [];
this.subscriptions.push(trigger.pipe(skip(1)).subscribe((e) =>
this.invokeRequest(callConfig, e))
);
You can use switchMap in the pipe like below:
private subscriptions: Subscription[] = [];
this.subscriptions.push(
trigger.pipe(
skip(1),
switchMap((e) => this.invokeRequest(callConfig, e))
).subscribe(resp => {
// do something with the `invokeRequest` response
})
);
This brings two main benefits:
As you pointed out, when trigger emits a new value, the previous invokeRequest is cancelled (if it's still pending) and a new one is started.
When you unsubscribe() your subscriptions (e.g when the component is destroyed), if there is some request pending, it's cancelled too. The way it was before (being called within the trigger.subscribe() callback) it would not cancel the request.
I have an observable that returns items, I need only one specific item and I want to add some extra fields to it from a http request which is also observable. Code below is how I'm trying to achieve that but it doesn't work. I need all observables to complete to get the full item data in ngOnInit. What am I missing?
ngOnInit() {
myItemsObservable$(this.store, items, items.data)
.map(items => items.find(
item => {
return item.id === id
}
))
.concatMap(item => {
return this.apiService.get(`/items/${item.id}/extradata`).map(extra => ({
...item,
extra
}))
})
.subscribe(item => {
// I expect item to have extra fields here.
this.item = item
})
// this.item here should already be complete.
}
Let's look at a simplified version of your code:
1 function ngOnInit() {
2 myItemsObservable$().subscribe(item => this.item = item);
3 console.log(this.item); // undefined
4 }
You are essentially calling two functions that get executed immediately one after the other.
Line 2 creates a subscription object which initiates the flow of data inside the observable. But... execution is not paused after line 2. So then, Line 3 is executed, before the asynchronous tasks within the observable have been completed. This is why this.item is still undefined on line 3.
Hopefully, you can see why your comment is not correct:
// this.item here should already be complete.
You are passing a function (item => this.item = item) to the subscribe() method that handles emissions from the observable when they occur. This is the place in your code where you actually have the emitted value.
So, if we move the console.log() inside the subscribe, this.item would no longer be undefined:
1 function ngOnInit() {
2 myItemsObservable$().subscribe(item => {
3 this.item = item;
4 console.log(this.item); // not undefined :-)
5 });
6 }
To address the two parts of your question:
How to add extra fields in RxJS observable
You are already doing this. You've used the map and concatMap operators to modify values emitted by the source observable into your desired value.
...and wait for it to complete?
Well, you don't "wait" for it per se. With RxJS, you are defining the behavior of how the data flows. The only place you have access to the actual data is inside the subscribe.
But... instead of subscribing, then copying the data from the observable to another variable, you can simply reference the observable directly in other parts of your code.
Let's break your code up into a few different parts so it's easier to see how we can reference different observable sources without subscribing:
id$ = this.route.paramMap.pipe( // This could come from a form control input
params => params.get('id') // or some other observable source.
);
allItems$ = myItemsObservable$(this.store, items, items.data);
getItem$(id) {
return this.allItems$.pipe(
map(items => items.find(i.id === id))
);
}
getExtraData$(id) {
return this.apiService.get(`/items/${id}/extradata`);
}
item$ = this.id$.pipe(
switchMap(id => getItem$(id)),
switchMap(item => this.getExtraData(item.id).pipe(
map(extra => ({ ...item, ...extra }))
))
);
}
See how the definition of item$ starts with the id$? This means that whenever id$ emits a new value, item$ will automatically call getItem$(), then getExtraData() then emit this new item. We didn't need to subscribe to make that happen.
We can simply define an observable to start with another observable then .pipe() the emissions and transform them to suit our needs.
We've essentially designed an observable that will emit any time that item in the store changes, or whenever our selected id$ emits a new value. In a sense, we've built up item$ to represent our item and it will always be up to date, including having its "extra data" appended. This is very powerful. Now we can just use it.
Notice the definition of item$ doesn't need to be in ngOnInit; it can actually go directly on your component.
It's true we could subscribe in our component... but we can usually just use the AsyncPipe in the template:
<div *ngIf="item$ | async as item">
<h1>{{ item.name }}</h1>
<ul>
<li>{{ item.description }}</li>
<li>{{ item.someProperty }}</li>
</ul>
</div>
If you find yourself often subscribing in your component, only to copy the data to a local variable, just to be consumed by your template; I would encourage you to pause and ask yourself it its really necessary. Most of the time you can define an observable that emits exactly the data your view needs without ever needing to subscribe.
RxJS provides many operators and static functions that make it easy to create observables with a variety of common behaviors.
Are you using an old version of angular and rxjs? In the current way, map is not a method of observables.
Instead, I think you would be looking for something like this;
function ngOnInit() {
myItemsObservable$(this.store, items, items.data)
.pipe(
filter(item => item.id === id), // assuming your observable emits each item separately. Otherwise, use your map statement above.
take(1), // do you need this? I'm guessing you wouldn't.
concatMap(item => this.apiService.get(`,/items/${item.id}/extradata`)
.pipe(
map(extra => Object.assign({}, item, {extra})) // if I understand what you are wanting here, otherwise replace "{extra}" with "extra", or go back to your original notation
)
)
)
.subscribe(
item => {
// I expect item to have extra fields here.
this.item = item
}
)
// this.item here should already be complete.
}
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!
Ok, this is a quick one, i'm kinda exhausted already and am confusing myself :D
I'm working with angular2 and RxJS Observables.
I have a service with a property "data", which is an Observable that get's set in the constructor, and a method to return this observable to subscribe to.
export class test{
private data: Observable<Object>
constructor(private http: Http){
this.data = this.http.get(url).map( res => res.json());
}
getData(): Observable<Object>{
return this.data
}
}
I have worked wit replaySubjects a while ago to always emit all values of the sequence to new subscribers. However, with the code above the Observable seems to emit it's latest value to new subscribers. Is this intended?
test(i: number) {
if (i > 0) {
setTimeout( () => {
this.dataService.getData().subscribe( (data) => {
this.debug.log(data);
this.test(i-1);
});
}, 2000);
}
}
test(4)
I get a value for every iteration. I am confused, 'cause a year ago when i wanted that behaviour, i got no new values when subscribing 'too late'.
Essentially, i just want to cache the result of the http.get, and deliver the same value to all subscribers, instead of making a new http request for every subscription (returning the http.get(url).. in getData())
I know this question is a bit old, but the answers seem to me quite confusing.
The Observable you return from the method getData() is just that, an Observable. So every time a consumer subscribes it gets the response. So it is working fine, but it is indeed making a new request every time.
In order to cache the result there are plenty of ways to do it depending on the behavior you want. To just cache a single request I would recommend t use the #publishBehavior operator:
export class test{
private data: Observable<Object>;
constructor(private http: Http){
this.data = this.http.get(url)
.map(res => res.json())
.publishBehavior([])
.refCount();
}
getData(): Observable<Object>{
return this.data;
}
}
The parameter passed to the publishBehavior is the initial value. With this two operators, the request will be made when the first subscriber arrived. Next subscribers will get the cached answer.
In others answers the use of Subjects has been suggested. The publishBehavior is using subjects under the hood. But to directly call next() it is consider bad practice unless there is no other remedy, and thats not the case for this scenario in my opinion. Even if you use Subjects directly, it will be wise to return an Observable to the Components by using the #asObservable() operator so the component won't have access to the next, error and complete methods.
No. You need to use Subject for this. It has a method next() to which you will send your newly arrived property so that it pushes it to the subscribers.
In addition to this, you should create a service that will be a singleton. Whenever your components instantiate it in a constructor, they will receive the object already formed with all the data. There will be no need to fetch the data every time.
Also, instead of instantiating your data in the constructor, implement OnInit and do the calls to the server from there.
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.