Lets say i have a subject that represents pages in a paginateable table:
pageCount$
I then pipe that into a new variable which goes of to an API to get the data for the table:
const tableData$ = pageCount$.pipe(switchMap(pageCount => getTableData(pageCount)));
This works great, every time i emit a new page tableData$ emits the data for that page.
Now here comes the problem and the question i wish to solve.
On my page i also wish to use that table data to display averages of what it currently contains. So i thought i could just pipe tableData$ through a map that performs those averages.
const averageData$ = tableData$.pipe(map(data => performAverages(data)));
This works, but because every-time i subscribe to each variable it creates a new instance my API call happens twice. Once for the tableData$ subscription and once for the averageData$ subscription. I understand this behavior is by design however.
It feels like i want to use some sort of tap/fork operator but i don't think such an operator exists.
Is it even possible to perform these tasks whilst only making the api call once?
Thanks
You can use the share operator to achieve this.
First create the observable that calls the API, and pipe it with share
https://www.learnrxjs.io/learn-rxjs/operators/multicasting/share.
Then the resulting observable can be subscribed twice, both subscription will receive the same results, without the shared observable being called twice ('multicasting').
That should give you something along the lines of :
const tableData$ = pageCount$.pipe(
switchMap(pageCount => getTableData(pageCount)),
tap(_ => console.log('API called')),
share()
);
// subscribe to tabledata$ twice
tableData$.subscribe(_ => console.log('get and use data once'));
tableData$.subscribe(_ => console.log('get and use data a second time'));
(to be tested, feedback appreciated!)
What about something like this
const tableData$ = pageCount$.pipe(
switchMap(pageCount => getTableData(pageCount).pipe(
map(data =>
const avg = performAverages(data);
return {data, avg}
)
))
);
This way you get an object containing both table data and its average, which is what I understand you are looking for.
Related
I'm currently building a Vue app that consumes data from the Contentful API. For each entry, I have a thumbnail (image) field from which I'd like to extract the prominent colours as hex values and store them in the state to be used elsewhere in the app.
Using a Vuex action (getAllProjects) to query the API, run Vibrant (node-vibrant) and commit the response to the state.
async getAllProjects({ commit }) {
let {
fields: { order: order }
} = await api.getEntry("entry");
let projects = order;
projects.forEach(p =>
Vibrant.from(`https:${p.fields.thumbnail.fields.file.url}`)
.getPalette()
.then(palette => (p.fields.accent = palette.Vibrant.hex))
);
console.log(projects);
// Commit to state
commit("setAllProjects", projects);
}
When I log the contents of projects right before I call commmit, I can see the hex values I'm after are added under the accent key. However, when I inspect the mutation payload in devtools, the accent key is missing, and so doesn't end up in the state.
How do I structure these tasks so that commit only fires after the API call and Vibrant have run in sequence?
You cannot add a property to an object in Vue and have it be reactive; you must use the Vue.set method.
Please try replacing that forEach block with the following, which adds the new property using Vue.set:
for (i=0; i<projects.length; i++)
Vibrant.from(`https:${projects[i].fields.thumbnail.fields.file.url}`)
.getPalette()
.then(palette => (Vue.set(projects[i].fields, accent, palette.Vibrant.hex)))
);
UPDATE: changing the format from forEach to a conventional for loop may be gratuitous in this case, since the assignment being made is to an object property of projects and not to a primitive.
I'm not spending a lot of time on StackOverflow, and if the above answer works, I am happy for you indeed.
But I expect from that answer you will get console warnings telling you not to mutate state directly.
Now when this happens, it's because while Vue.set(), does in fact help Vue understand reactively a change has been made, potentially deeply nested in an object.
The problem here is that since you are looping the object, changing it all the time, the commit (Mutator call) is not the one changing state - Vue.set() is actually changing it for every iteration.
I have two async requests I am trying to fulfill, the second based upon the results of the first. The way I am trying to do this is by:
Listen for success of first action: actions.GetAllItems
Select out from the store the relevant items based on ID: this.store.select(selectors.getItemsById)
Map over the returned IDs so I can make the second call for each item in the array of IDs returned by the first call
Put results in redux store, render to view.
The way I have now does successfully put it in my redux store. However since it's just vanilla Array.map it doesn't return an observable. Which means the observable isn't stored in this.details$, which means it does not render in my template with {{ details$ | async | json }}
How can I achieve this secondary XHR call based upon the results of the first?
ngOnInit() {
this.store.dispatch(new actions.GetAllItems())
this.details$ = this.actions$.pipe(
ofType(actions.types.GetAllItemsSuccess),
mergeMap(() => {
return this.store.select(selectors.getItemsById); // filter to multiple items based on item ID
}),
map((items: models.IItemGeneralResponse[]) => {
items.map(item => { // sync map does not seem like it belongs in rxjs
this.store.dispatch(
new actions.GetItemDetail(item.id)
);
});
})
);
}
You are trying to do ngrx effects stuff in your angular component. Use effects to handle side effects (calls to the backend/fetching data from local storage etc...) and make your component to watch for a piece of your state via a selector. Let's summarize like this -
Your component [or your guard or resolver] will just dispatch an action to the store.
If you set up a reducer for that action then your reducer will be called first otherwise it will go to step 3
In your effect, you are watching for the dispatched action. Your effect will make the first call and then from the response of the first call, it will make the second call and then it will update the state in your store [or piece of the state] which is being watched by your component by dispatching the respective actions to the store.
This is a typical workflow [It may vary as per the need of the app but the basic idea remains the same]. So keeping the basic idea lets modify your code like this -
In your component
sliceOfState$: Observable<any>; //change the type of observabe as per your app
ngOnInit() {
this.store.dispatch(new actions.GetAllItems())
//this observable will be used to render your data on UI
//you can use various rxjs operators to transform your data before shoing to UI
this.sliceOfState$ = this.store.select(//your selector which gives you sliceOfState);
}
Now In your effect -
#Effect()
this.details$ = this.actions$.pipe(
ofType(actions.types.GetAllItems),
switchMap(() => {
//here you call API which makes the call to backend which return allItems
return this.yourServiceWhichGetAllItems.getAllItems();
}),
switchMap(allItems => {
//now for each item you need to get its detail
//so forkJoin all the observables which calls the backedn for each item
const obs$ = allItems.map(item => this.yourServiceWhichGetDetails.getItemDetail(item));
return forkJoin(obs$);
})
map(allItemsWithDetails => {
//here you should call your action which will update the state in your store
return new actions.SetAllItemsDetails(allItemsWithDetails);
})
);
I have provided pseudo code which will give you an idea of how to achieve what you want to do. For more info, you can visit the official site of ngrx - https://ngrx.io/guide/effects
I have an async api call where I get an array of objects and then I map that to dynamically registered modules in my store. Something like this:
dispatch
// before this dispatch some api call happens and inside the promise
// iterate over the array of data and dispatch this action
dispatch(`list/${doctor.id}/availabilities/load`, doctor.availabilities);
The list/${doctor.id} is the dynamic module
action in availabilities module
load({ commit }, availabilities) {
const payload = {
id: availabilities.id,
firstAvailable: availabilities.firstAvailable,
timeslots: [],
};
// then a bunch of code that maps the availabilities to a specific format changing the value of payload.timeslots
commit('SET_AVAILABILITIES', payload)
}
mutation
[types.SET_TIMESLOTS](state, payload) {
console.log(payload);
state.firstAvailable = payload.firstAvailable;
state.id = payload.id;
state.timeslots = payload.timeslots;
}
When I check my logs for the console.log above each doctor has different arrays of time slots Exactly the data I want. However, in the vue developer tools and what is being rendered is just the last doctor's timeslots for all of the doctors. All of my business logic is happening in the load action and the payload in the mutation is the correct data post business logic. Anyone have any ideas why I'm seeing the last doctor's availabilities for every doctor?
It looks like you are assigning the same array (timeslots) to all doctors.
When you add an element to the array for one doctor, you mutate the array that all doctors are sharing.
However with the little code you show, it's difficult to know where is the exact problem.
I have an Observable listening to the URL and I am switching it to a getRows() which returns an Observable pulling data back from the API with the URL parameters. I want to be able get a Subscription reference for every emit that getRows() does. This is to show a loading indicator on the UI.
The current code:
this.tableSource = this.urlParamService.getParameterGroup(this.parameterPrefix)
.distinctUntilChanged()
.map(p => this.getRows(p))
.switch()
.share();
And then explicitly when I have changed the parameters I have been calling:
this.tableLoad = this.tableSource.take(1).subscribe((r) => this.rows = this.parseRows(r));
But I want to enable the component to update when external entities manipulate the URL and so I should be subscribing instead of sharing tableSource, so how can I get a Subscription everytime I call getRows(), is it possible?
I managed to solve it this way:
this.urlParamService.getParameterGroup(this.parameterPrefix)
.distinctUntilChanged()
.do(p => {
this.tableLoad = this.getRows(p).subscribe((r) => this.rows = this.parseRows(r));
})
.subscribe();
So I stopped trying to use both Observable sequences as one (even though one depends on the other), and just subscribed to the second as a side effect of the first.
I've got LOTS of Reducer Queries that I need to Link up to behavior subjects. At the moment I'm doing this, for every single one!
Is there a more concise way to express this?
this._higherCurveObservable = this.store.let(getCurveSummaryForInstrumentTimeframe(this._instrument, this._timeframe.Code));
this._higherCurveSubject = <BehaviorSubject<CurveSummary>>new BehaviorSubject(null).distinctUntilChanged();
this._higherCurveSubscription = this._higherCurveObservable.subscribe(x => this._higherCurveSubject.next(x));
this._higherCurveSubject.subscribe(x => this.higherCurveChange(x));
There should be no need to create seperate BehaviourSubjects, you could just base your required Observables on the store-stream.
(The only time when you would need something like this - and I'm not assuming you did this - would be the case when you are trying to abuse a BehaviourSubject to be able to take data from the store as well as to dispatch data from the side, in which case you should rethink the architecture, since this would go against the concepts of ngrx of having a single central store.)
So there are multiple ways to write this with less overhead.
Example 1: A perpetual subscription, that never unsubscribes (e.g. in a Service)
this.store
.let(getCurveSummaryForInstrumentTimeframe(this._instrument, this._timeframe.Code))
.distinctUntilChanged()
.do(x => this.higherCurveChange(x))
.subscribe();
Example 2: A temporary subscriptions, that should unsubscribe at a specific point in time (e.g. in a Component, it should unsubscribe when the Component is destroyed)
const higherCurve$: Observable<CurveSummary> = this.store
.let(getCurveSummaryForInstrumentTimeframe(this._instrument, this._timeframe.Code))
.distinctUntilChanged();
this._higherCurveSubscription = higherCurve$.subscribe(x => this.higherCurveChange(x));
// ... some time later
this._higherCurveSubscription.unsubscribe();