how to use combineLatest/zip but only react to emits of second observable and ignore emits from first observable - javascript

So I have this property initialization:
this.currentMapObject$ = zip(this.mapObjects$, this.currentMapObjectsIndex$,
(mapObjects, index) => mapObjects[index]);
I only want to emit this.currentMapObject$ if this.currentMapObjectsIndex$ emits but not if this.mapObjects$ emits.
As far as I know now this.currentMapObject$ will emit even if a single property of an item of this.mapObjects$ changes. I want to ignore all changes to this.mapObjects$ and only take its current value, but I DO want to react to this.currentMapObjectsIndex$ changes

You need to look at withLatestFrom:
Combines the source Observable with other Observables to create an Observable whose values are calculated from the latest values of each, only when the source emits.
https://rxjs.dev/api/operators/withLatestFrom
As per #BizzyBob suggestion:
this.currentMapObject$ = this.currentMapObjectsIndex$.pipe(
withLatestFrom(this.mapObjects$),
map(([index, mapObjects]) => {
// ...
}
)

Related

Modify data in stream used with angular async pipe reactive way

iam trying to use async pipes instead of manually subscribing in components, in case, iam just displaying data which i get from server, everything works fine. But what if i need to change displayed part of displayed data later, based on some event?
For example, i have a component which displays timetable, which can be later accepted by user. I create timesheet$ observable and i use it in my template with async pipe. For accepting i created subject, so i can emit, when user accepts the timesheet. But how do i combine these two streams(approvementChange$ and timesheet$), so the $timesheet gets updated? I was trying combineLatest, but it returns the latest values, so i cant decide, if the value from approvementChange stream is new or old. Any ideas how to solve this?
export class TimesheetComponent {
errorMessage: string = '';
timesheet$: Observable<Timesheet>;
private approvementSubject = new Subject<TimesheetApprovement>();
approvementChange$ = this.approvementSubject.asObservable();
constructor(
private planService: PlanService,
private statusService: StatusService,
private notifService: NotificationService
) {}
this.timesheet$ = this.statusService.appStatus.pipe(
switchMap((status) => {
return this.planService.getTimesheet(
status.selectedMonth,
status.selectedAgency.agNum
);
})
); //get data every time user changed selected month
approveTimesheet(isApproved: boolean = true) {
const approvement: TimesheetApprovement = {
isApproved: isApproved,
approveTs: new Date(),
isLock: true,
};
this.approvementSubject.next(approvement);
}
}
But what if i need to change displayed part of displayed data later, based on some event?
RxJS provides lots of operators for combining, filtering, and transforming observables.
You can use scan to maintain a "state" by providing a function that receives the previous state and the newest emission.
Let's look at a simplified example using a generic Item interface:
interface Item {
id : number;
name : string;
isComplete : boolean;
}
We will use 3 different observables:
initialItemState$ - represents initial state of item after being fetched
itemChanges$ - represents modifications to our item
item$ - emits state of our item each time a change is applied to it
private itemChanges$ = new Subject<Partial<Item>>();
private initialItemState$ = this.itemId$.pipe(
switchMap(id => this.getItem(id))
);
public item$ = this.initialItemState$.pipe(
switchMap(initialItemState => this.itemChanges$.pipe(
startWith(initialItemState),
scan((item, changes) => ({...item, ...changes}), {} as Item),
))
);
You can see we define item$ by piping the initialItemState$ to the itemChanges$ observable. We use startWith to emit the the initialItemState into the changes stream.
All the magic happens inside the scan, but the set up is really simple. We simply provide a function that accepts the previous state and the new change and returns the updated state of the item. (In this case, I'm just naively apply the changes to the previous state; it's possible this logic would need to be more sophisticated for your case.)
This solution is completely reactive. The end result is a clean item$ observable that will emit the updated state of the current item (based on id), whenever the id changes or the changes occur on the item.
Here's a StackBlitz where you can see this behavior.
you need to track only approvementChange
after that you can pick the latest timesheet via withLatestFrom
approvementChange$
.pipe(withLatestFrom(this.timesheet$))
.subscribe(([accepted, latestTimesheet]) => accepted ? save(latestTimesheet) : void 0)

how to handle same value from multiple datastreams in Angular RxJs

I have two different data streams which gives same object with modified property values. I want to write a single subscription so that whenever any of the two DataStream notifies about property modification I can reuse the same code.
const selectedItems$ = this.grid.onSelect.pipe(.... this gives me the selected object);
const updatedItem$ = this.fetchData.onUpdate.pipe(.....this gives me the updated object);
displayItem$(selection$, updatedItem$) {
(..here i want to get notified whenever there is change in).subscribe(item => {
this.displayLatest(item)
})
}
selectedItem$ and updatedItem$ can return same object with different property values when the already selected item is modified.
This is the first time I am working on RxJs and bit confused about this part. I searched in RxJS operators list (like concat, merge, combine) but most of the working examples are for different data structures. Also is there any other better way to achieve this?
You can use merge to create an observable that emits values from both source streams:
import { merge } from 'rxjs';
...
merge(selection$, updatedItem$)
.subscribe(item => {
this.displayLatest(item)
})

RxJS: How to combine multiple nested observables with buffer

Warning: RxJS newb here.
Here is my challenge:
When an onUnlink$ observable emits...
Immediately start capturing values from an onAdd$ observable, for a maximum of 1 second (I'll call this partition onAddBuffer$).
Query a database (creating a doc$ observable) to fetch a model we'll use to match against one of the onAdd$ values
If one of the values from the onAddBuffer$ observable matches the doc$ value, do not emit
If none of the values from the onAddBuffer$ observable matches the doc$ value, or if the onAddBuffer$ observable never emits, emit the doc$ value
This was my best guess:
// for starters, concatMap doesn't seem right -- I want a whole new stream
const docsToRemove$ = onUnlink$.concatMap( unlinkValue => {
const doc$ = Rx.Observable.fromPromise( db.File.findOne({ unlinkValue }) )
const onAddBuffer$ = onAdd$
.buffer( doc$ ) // capture events while fetching from db -- not sure about this
.takeUntil( Rx.Observable.timer(1000) );
// if there is a match, emit nothing. otherwise wait 1 second and emit doc
return doc$.switchMap( doc =>
Rx.Observable.race(
onAddBuffer$.single( added => doc.attr === added.attr ).mapTo( Rx.Observable.empty() ),
Rx.Observable.timer( 1000 ).mapTo( doc )
)
);
});
docsToRemove$.subscribe( doc => {
// should only ever be invoked (with doc -- the doc$ value) 1 second
// after `onUnlink$` emits, when there are no matching `onAdd$`
// values within that 1 second window.
})
This always emits EmptyObservable. Maybe it's because single appears to emit undefined when there is no match, and I'm expecting it not to emit at all when there is no match? The same thing happens with find.
If I change single to filter, nothing ever emits.
FYI: This is a rename scenario with file system events -- if an add event follows within 1 second of an unlink event and the emitted file hashes match, do nothing because it's a rename. Otherwise it's a true unlink and it should emit the database doc to be removed.
This is my guess how you could do this:
onUnlink$.concatMap(unlinkValue => {
const doc$ = Rx.Observable.fromPromise(db.File.findOne({ unlinkValue })).share();
const bufferDuration$ = Rx.Observable.race(Rx.Observable.timer(1000), doc$);
const onAddBuffer$ = onAdd$.buffer(bufferDuration$);
return Observable.forkJoin(onAddBuffer$, doc$)
.map(([buffer, docResponse]) => { /* whatever logic you need here */ });
});
The single() operator is a little tricky because it emits the item that matches the predicate function only after the source Observable completes (or emits an error when there're two items or no matching items).
The race() is tricky as well. If one of the source Observables completes and doesn't emit any value race() will just complete and not emit anything. I reported this some time ago and this is the correct behavior, see https://github.com/ReactiveX/rxjs/issues/2641.
I guess this is what went wrong in your code.
Also note that .mapTo(Rx.Observable.empty()) will map each value into an instance of Observable. If you wanted to ignore all values you can use filter(() => false) or the ignoreElements() operator.

How to push to Observable of Array in Angular 4? RxJS

I have a property on my service class as so:
articles: Observable<Article[]>;
It is populated by a getArticles() function using the standard http.get().map() solution.
How can I manually push a new article in to this array; One that is not yet persisted and so not part of the http get?
My scenario is, you create a new Article, and before it is saved I would like the Article[] array to have this new one pushed to it so it shows up in my list of articles.
Further more, This service is shared between 2 components, If component A consumes the service using ng OnInit() and binds the result to a repeating section *ngFor, will updating the service array from component B simultaneously update the results in components A's ngFor section? Or must I update the view manually?
Many Thanks,
Simon
As you said in comments, I'd use a Subject.
The advantage of keeping articles observable rather than storing as an array is that http takes time, so you can subscribe and wait for results. Plus both components get any updates.
// Mock http
const http = {
get: (url) => Rx.Observable.of(['article1', 'article2'])
}
const articles = new Rx.Subject();
const fetch = () => {
return http.get('myUrl').map(x => x).do(data => articles.next(data))
}
const add = (article) => {
articles.take(1).subscribe(current => {
current.push(article);
articles.next(current);
})
}
// Subscribe to
articles.subscribe(console.log)
// Action
fetch().subscribe(
add('article3')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.2/Rx.js"></script>
Instead of storing the whole observable, you probably want to just store the article array, like
articles: Article[]
fetch() {
this.get(url).map(...).subscribe(articles => this.articles)
}
Then you can manipulate the articles list using standard array manipulation methods.
If you store the observable, it will re-run the http call every time you subscribe to it (or render it using | async) which is definitely not what you want.
But for the sake of completeness: if you do have an Observable of an array you want to add items to, you could use the map operator on it to add a specified item to it, e.g.
observable.map(previousArray => previousArray.concat(itemtToBeAdded))
ex from angular 4 book ng-book
Subject<Array<String>> example = new Subject<Array<String>>();
push(newvalue:String):void
{
example.next((currentarray:String[]) : String[] => {
return currentarray.concat(newValue);
})
}
what the following says in example.next is take the current array value Stored in the observable and concat a new value onto it and emit the new array value to subscribers. It is a lambda expression.I think this only works with subject observables because they hold unto the last value stored in their method subject.getValue();

How to switch to another Observable when a source Observable emits but only use latest value

I have two observables
load$ is a stream of load events, emitted on startup or when a reload button is clicked
selection$ is a stream of selected list items, emitted when a list item is selected
I'm wondering if there is an operator that lets me (A) get the latest value emitted by selection$ whenever load$ emits, while (B) ignoring the value emitted by load$.
I was able to accomplish (A) by using withLatestFrom but that doesn't satisfy (B) because withLatestFrom does not ignore the source observable.
load$.withLatestFrom(selection$).subscribe(([_, latestSelection) => {
// _ is not needed and I don't like to write code like this
// I only need the latest selection
});
I've looked at switchMapTo which satisfies (B) but not (A) without also using first() or take(1).
load$.switchMapTo(selection$.take(1)).subscribe(latestSelection => {
// I would like to avoid having to use take(1)
});
I'm looking for a hypothetical switchMapToLatestFrom operator.
load$.switchMapToLatestFrom(selection$).subscribe(latestSelection => {
// switchMapToLatestFrom doesn't exist :(
});
Unfortunately it doesn't exist and I don't like the two other ways that I came up with because it's not immediately obvious why the code contains take(1) or unused arguments.
Are there any other ways to combine two observables so that the new observable emits only the latest value from the second observable whenever the first observable emits?
withLatestFrom takes an optional project function that's passed the values.
You can use it to ignore the value from load$ and emit only the latest selection:
load$
.withLatestFrom(selection$, (load, selection) => selection)
.subscribe(selection => {
// ...
});
use "sample" operator
In your case:
selection$.pipe(sample(load$)).subscribe(
// the code you want to run
)

Categories

Resources