I have this kind of strange situation in this reducer action
export const myAction = payload => {
const x = { type: MY_ACTION, payload }
console.log('sending action', x.payload)
return x
}
The above example works in that I can log the payload (which is a navigator.geolocation position to the console.
But when I want to do any sort of transforms such as stringifing this data, it no longer is possible to log it:
export const myAction = payload => {
const x = { type: MY_ACTION, payload }
const st = JSON.stringify(x)
console.log('stringified', st)
return x
}
The above results in st being blank and not logged.
Is there some magic going on here that I don't understand?
Any advice, pointers to docs, etc would be appreciated
Thanks
I realized that the answer was that the navigator.geolocation object is not stringifi-able ...
Related
I'm new in rxjs world and I have to rewrite some code. So, I draft my ideas.
I have a request, which could fail and return an observable. I simulate that with the ob-variable and two map operations. Then, I try to catch an error. I need the result in my local variable selected and raise an event on isChanged. I call my function now via subscription. I don't need a result.
My question: Is one big pipe enough and can I use following approach for the work with my local variables?
import { of, map, Observable, tap, Subject, throwError, EMPTY } from 'rxjs';
import { catchError } from 'rxjs/operators';
let selected = 0;
const isChanged = new Subject<number>();
function myfunc(): Observable<boolean> {
const ob = of(1,3,4,5,7);
return ob.pipe(
// simulates a http request
map(v => v*2),
// simulates a rare error condition
map(v => {
// if (v === 8) { throw `four`; }
if (v === 10) { throw `ten`; }
return v;
}),
// play with different failure situations
catchError((e) => {
if (e === `four`) {
return of(4);
}
if (e === `ten`) {
return EMPTY;
}
console.warn(e);
return throwError(e);
}
),
// I need the result in a local variable
// I need a information about success
// I need the result not really
map((res) => {
selected = res;
isChanged.next(res);
return true;
})
);
}
console.log(`a: selected is ${selected}`);
isChanged.subscribe(v =>
console.log(`b: isChanged received: ${v}, selected is ${selected}`));
console.log(`c: selected is ${selected}`);
// I have to call the function
myfunc().subscribe((b) => {
console.log(`d: selected is ${selected}`);
});
I create the world in Stackblitz too:
https://stackblitz.com/edit/rxjs-6fgggh?devtoolsheight=66&file=index.ts
I see results like expected. But I'm not sure if all ideas are the right way to solve all problems.
Thanks for you thought.
So I'm trying to build a resolver for graphQL which is supposed to return an array of objects. The values for these objects come from a series of TypeORM selecting operations. And when I tried asking for a response in the graphql playground I only got empty arrays, so I started debugging the resolver using console.logs, but the thing is, inside the forEach loops I use the code seems to have the desired result: an array with objects in it. But when I log the same array right before returning it is empty:
#Query(() => [response])
async getTeacherFromSubjectName(
#Arg("subjectName") subjectName: string,
): Promise<response[] | undefined> {
const subject = await Subject.findOne({name: subjectName});
const subjectId = subject?.id;
let responseArray: response[] = [];
const qb = await getConnection()
.createQueryBuilder()
.select("teacher")
.from(Teacher, "teacher")
.where(`teacher.subjectId = ${subjectId}`)
.getMany()
qb.forEach(async (teacher) => {
const qb = await getConnection()
.createQueryBuilder()
.select("lectureTime")
.from(LectureTime, "lectureTime")
.where(`lectureTime.teacherId = ${teacher.id}`)
.getMany()
responseArray.push( {
teacher: teacher.name,
lectures: qb,
} );
console.log(responseArray) // [{ teacher: 'Dirceu', lectures: [ [LectureTime] ] }, { teacher:
'Patrícia', lectures: [ [LectureTime], [LectureTime] ] } ]
})
console.log(responseArray) // []
return responseArray;
}
What I get on the console is the following:
link to image
I actually have no idea of what is going on here, you can see in the image that the order of the logs is inverted (log right before return is circled in blue).
I am certain that it is a silly problem and if you guys could point it out for me I would be very thankful.
As xadm said on the comments, you are not awaiting for the promises you are creating inside forEach. In that case you need to map to the promises, so you'll have an array of the Promises and them await them all.
// Changed to map so you get the return values
const promises = qb.map(async (teacher) => {
const qb = await getConnection()
.createQueryBuilder()
.select("lectureTime")
.from(LectureTime, "lectureTime")
.where(`lectureTime.teacherId = ${teacher.id}`)
.getMany()
responseArray.push( {
teacher: teacher.name,
lectures: qb,
} );
console.log(responseArray);
});
// Await for all the promises to be completed, regardless of order.
await Promise.all(promises);
I have an input field which when submitted makes a http call and then plots a graph. When I click on any node of graph, same http call is made and results are appended to the previous results and graph is updated. It is working fine till here. I am using scan operator to update my resultset. Now, what I want is to reset the resultset (ie - return new original response) whenever I am submitting the input form and append to resultset when graph node is clicked. Any ideas on how this can be achieved? Mainly how can I reset this stream on form submit? Or how can I show new data on form submit and updated data on node click
Here linkingDetailsByAccount$ makes the http call and gets the data from the server.
this.linkingDetailsByAccountSubject.next(account);
Same code is called on node click as well as on form submit which then activates my stream.
graph$ = this.linkingDetailsByAccount$.pipe(
pluck('graph'),
scan((linkedDetails, adjacency) => {
const { nodes: linkedNodes = [], edges: linkedEdges = [] } = linkedDetails;
const { nodes: newNodes = [], edges: newEdges = [] } = adjacency;
const updatedNodes = differenceBy(newNodes, linkedNodes, 'id');
const updatedEdges = differenceWith(
newEdges,
linkedEdges,
(newEdge: VisEdge, existingEdge: VisEdge) => newEdge.from === existingEdge.to
);
const allNodes = [...linkedNodes, ...updatedNodes];
const allEdges = [...linkedEdges, ...updatedEdges];
return {
nodes: allNodes,
edges: allEdges
};
}, {} as NodesEdges)
);
Appreciate any inputs on this.
Thanks,
Vatsal
Edit: Updated answer when I received more details from OP.
How I would do it is turn it into a mini Redux like state manager.
So the scan operator should take in functions or event objects.
First you want to store the first initial state from the initial HTTP call you make. You will use this object to reset your state on form submission.
Then create a graphEvents subject.
interface UpdateGraphEvent {
type: 'Update';
account: any;
}
interface ResetGraphEvent {
type: 'Reset';
account: any;
}
type GraphEvent = UpdateGraphEvent | ResetGraphEvent;
this.graphEvents$ = new Subject<GraphEvent>();
Then you can use your new graphEvents$ subject to replace uses of linkingDetailsByAccountSubject.
// When you want to update with new data.
this.graphEvent$.next({type: 'Update', account: account});
// when you want to reset with initial data.
this.graphEvent$.next({type: 'Reset', account: this.initialAccount});
Then use it in your stream.
graph$ = this.graphEvent$.pipe(
pluck('graph'),
scan((linkedDetails, event: GraphEvent) => {
if (event.type === 'Reset') {
return {
nodes: event.account.nodes,
edges: event.account.edges,
}
}
const { nodes: linkedNodes = [], edges: linkedEdges = [] } = linkedDetails;
const { nodes: newNodes = [], edges: newEdges = [] } = event.account;
const updatedNodes = differenceBy(newNodes, linkedNodes, 'id');
const updatedEdges = differenceWith(
newEdges,
linkedEdges,
(newEdge: VisEdge, existingEdge: VisEdge) => newEdge.from === existingEdge.to
);
const allNodes = [...linkedNodes, ...updatedNodes];
const allEdges = [...linkedEdges, ...updatedEdges];
return {
nodes: allNodes,
edges: allEdges
};
}, {} as NodesEdges)
);
The graphEvent$ will be a Subject that emits those events (GraphEvent).
I've pairs of events: add1/add2/etc and remove1/remove2/etc. I'd like the following:
when an add1 is emitted on the stream
if DELAY transpires with no new add* emissions
emit remove1
if add* is emitted
emit remove1 for add1 immediately
emit remove* for add* after DELAY
This should continue for all emissions of add* on the stream.
Here's a test I've written using RxJS marble testing for this case:
import test from 'tape'
import { set, lensPath } from 'ramda'
import { TestScheduler } from 'rxjs/testing'
import hideAfterDelay from '../another/file'
import { actionCreators } from '../another/dir'
const prefix = 'epics -> notifications'
test(`${prefix} -> hideAfterDelay`, t => {
t.plan(1)
const scheduler = new TestScheduler(t.deepEqual)
const actionMap = {
a: createAddAction('hello!'),
b: createAddAction('goodbye!'),
x: actionCreators.notifications.remove('hello!'),
y: actionCreators.notifications.remove('goodbye!')
}
scheduler.run(({ cold, expectObservable }) => {
const actionStream = cold('a-------a-b-a------', actionMap)
const expected = '-----x-----x-y----x'
const actual = hideAfterDelay(5)(actionStream)
expectObservable(actual).toBe(expected, actionMap)
})
})
function createAddAction (name) {
const action = actionCreators.notifications.add(name)
const lens = lensPath(['payload', 'id'])
return set(lens, name, action)
}
I think the test is representative of the behavior I described above and that I want.
How can I write this observable? I've tried using timer and race but I haven't been able to get this working...
This is an epic using redux-observable, btw.
Using RxJS v6
Ok, I think I got a working solution using a closure and slightly modifying my test assertion.
First, the expected marble diagram should look like this
// input: a-------a-b-a------
// - expected: -----x-----x-y----x
// + expected: -----x----x-y----x
//
// Note above that the middle x and y emit at the same time as new
// `add*` actions on the source stream instead of one frame later
With that small change—which still feels consistent with my description in the question—I was able to get my test passing with the following:
import { of, timer, empty } from 'rxjs'
import { switchMap, mapTo, tap, merge } from 'rxjs/operators'
import { ofType } from '../operators'
import actionTypes from '../../actionTypes/notifications'
import { actionCreators } from '../..'
export default (delay = 3000) => actionStream => {
let immediateRemove
return actionStream.pipe(
ofType(actionTypes.ADD),
switchMap(action => {
let obs = empty()
if (immediateRemove) {
obs = of(immediateRemove)
}
const remove = actionCreators.notifications.remove(action.payload.id)
immediateRemove = remove
return obs.pipe(
merge(
timer(delay).pipe(
tap(() => {
immediateRemove = null
}),
mapTo(remove)
)
)
)
})
)
}
I've no idea if this is the best or right way to solve it, but I'm fairly certain it is a way.
I have this code, and failing to understand why I am not getting inside the map function (where I have the comment "I AM NEVER GETTING TO THIS PART OF THE CODE"):
export const fiveCPMonitoringLoadEpic = (action$, store) =>
action$
.ofType(
FIVE_CP_MONITORING_ACTION_TYPES.LOAD_FIVE_CP_MONITORING_DATA_STARTED
)
.debounceTime(250)
.switchMap(action => {
const params = action.params;
const siteId = { params };
// getting site's EDC accounts (observable):
const siteEdcAccount$ = getSiteEDCAccountsObservable(params);
const result$ = siteEdcAccount$.map(edcResponse => {
// getting here - all good so far.
const edcAccount = edcResponse[0];
// creating another observable (from promise - nothing special)
const fiveCPMonitoringEvent$ = getFiveCPAndTransmissionEventsObservable(
{
...params,
edcAccountId: edcAccount.utilityAccountNumber
}
);
fiveCPMonitoringEvent$.subscribe(x => {
// this is working... I am getting to this part of the code
// --------------------------------------------------------
console.log(x);
console.log('I am getting this printed out as expected');
});
return fiveCPMonitoringEvent$.map(events => {
// I NEVER GET TO THIS PART!!!!!
// -----------------------------
console.log('----- forecast-----');
// according to response - request the prediction (from the event start time if ACTIVE event exists, or from current time if no active event)
const activeEvent = DrEventUtils.getActiveEvent(events);
if (activeEvent) {
// get event start time
const startTime = activeEvent.startTime;
// return getPredictionMeasurementsObservable({...params, startTime}
const predictions = getPredictionMock(startTime - 300);
return Observable.of(predictions).delay(Math.random() * 2000);
} else {
// return getPredictionMeasurementsObservable({...params}
const predictions = getPredictionMock(
DateUtils.getLocalDateInUtcSeconds(new Date().getTime())
);
return Observable.of(predictions).delay(Math.random() * 2000);
}
});
can someone please shed some light here?
why when using subscribe it is working, but when using map on the observable it is not?
isn't map suppose to be invoked every time the observable fires?
Thanks,
Jim.
Until you subscribe to your observable, it is cold and does not emit values. Once you subscribe to it, the map will be invoked. This is a feature of rxjs meant to avoid operations that make no change (= no cunsumer uses the values). There are numerous blog posts on the subject, search 'cold vs hot obserables' on google