I've found myself with a mixed object of values and observables. Something like:
const mixedObject = {
field1: "Hello",
field2: Rx.Observable.of('World'),
field3: Rx.Observable.interval(1000),
};
What would be the best way of doing something like a combineLatest on this object to get a stream of plain objects.
For example, I'd like to do something like:
Rx.Observable.combineLatest(mixedObject).subscribe(plainObject => {
console.log(plainObject.field1, plainObject.field2, plainObject.field3);
// Outputs:
// > Hello World 0
// > Hello World 1
// > Hello World 2
});
You didn't specify if field1 is always a value while field2 and field3 are observables. I will try to give a more general solution.
function mixedToSync(obj: any): Observable<any> {
return Observable.combineLatest(
Object.keys(obj).map((key: string) => {
let value = obj[key];
if(Observable.prototype.isPrototypeOf(value)){
return value.map(v => ({[key]: v}));
} else {
return Observable.of({[key]: value});
}
}))
.map(propsObservables => Object.assign({}, propsObservables));
}
and then you can use it on your object:
mixedToSync(mixedObject).subscribe(v => console.log(v));
Or to match your example:
mixedToSync(mixedObject).subscribe(v => console.log(v.field1, v.field2, v.field3));
This is now in RXJS 7.
const res = combineLatest({ foo: a$, bar: b$, baz: c$ });
res.subscribe(({ foo, bar, baz }) => { console.log(foo, bar, baz); });
Related
When writing a Jest test.each() test using an enum like below
enum MyEnum {
FirstValue,
SecondValue,
ThirdValue
}
describe('my example', () => {
test.each([MyEnum.FirstValue, MyEnum.SecondValue, MyEnum.ThirdValue])(
'%o',
(input: MyEnum) => {
expect(input).toBeDefined();
}
);
});
The output text is:
my example
0
1
2
I would like it to be
my example
FirstValue
SecondValue
ThirdValue
Using other formatting parameters like %p or %j like listed in the Jest docs don't change anything
I just thought of an answer, I could use describe.each in stead:
describe.each(testcases)('my test', (input: MyEnum) => {
test(`${MyEnum[input]}`, () => {
expect(input).toBeDefined();
});
});
My first observable returns an array of Persons. I want to update each person of that array with a list of clients from second observable. How do I do that? So far I have this:
const json: Person[] = [new Person('Alice'), new Person('Bob')];
const resultsObservable = new Observable<string[]>(subscriber => {
setTimeout(() => {
subscriber.next(['Client1', 'Client2', 'Client3']);
subscriber.complete();
}, 1000);
});
of(json).pipe(
switchMap( dataArray => {
return from(dataArray);
}),
map((x: Person) => {
resultsObservable.subscribe(r => {
x.clients = r;
});
return x;
}),
).subscribe(value => {
console.log(value);
});
}
Person:
export class Person{
name: string;
clients?: string[];
constructor(name: string) {
this.name = name;
}
}
But the problem is that return happens before the values are set, so at the end value of person.clients is undefined. How do I fix this? Or what is a better way to achieve what I'm trying to do?
Ok I think I found what I was looking for:
const result = persons.pipe(
mergeMap(p => resultsObservable.pipe(map(clients => {
p.clients = clients;
return p;
}))),
);
result.subscribe(p => console.log(p));
I need to make two calls to Firebase (as it doesn't support OR queries) and merge the output into one array at the end to return to the calling service.
I have something that gets pretty close but it outputs a 2D array of arrays (one for each call to Firebase). I've tried a few things and this is the best I can get to. Any help on tidying up the below would be great.
getAllFriends(): Observable<[Friendship[], Friendship[]]> {
const invitesSent = from(this.afAuth.currentUser.then(user => {
return user.uid;
}))
.pipe(
switchMap(
userid => {
return this.db.collection('friendships', ref => ref.where('inviter', '==', userid)).snapshotChanges().pipe(map(actions => {
return actions.map(action => {
const data = new Friendship(action.payload.doc.data());
data.id = action.payload.doc.id;
console.log(data);
return data;
});
}));
}
)
);
const invitesReceived = from(this.afAuth.currentUser.then(user => {
return user.uid;
}))
.pipe(
switchMap(
userid => {
return this.db.collection('friendships', ref => ref.where('invitee', '==', userid)).snapshotChanges().pipe(map(actions => {
return actions.map(action => {
const data = new Friendship(action.payload.doc.data());
data.id = action.payload.doc.id;
console.log(data);
return data;
});
}));
}
)
);
return combineLatest([invitesSent, invitesReceived]);
}
Friendship is just an object with property: value pairs, nothing special.
I have tried then putting a .pipe() after this returned observable but that just stops the subscription firing in the calling service.
What about returning, at the end, something like this
return combineLatest([invitesSent, invitesReceived]).pipe(
map(([frienships_1, friendships_2]) => ([...friedships_1, ...friendships_2]))
)
Reproduction :
// #flow
type A = { key: string, value: string};
const a:A = {
key: 'a',
value: 'a'
};
const foo = ():Promise<A> => {
return new Promise(function(resolve, reject){
setTimeout(function(){
resolve(a);
}, 1000);
});
}
const bar = async ():A => {
const res:A = ((await foo()):any);
return res;
}
bar();
Try it on flow.org/try
Context :
When calling a function called 'foo' returning a promise with await, the type of the variable is still Promise.
Flow correctly interprets the value if we just return the variable, but triggers an error if we type the return of the function called 'bar'.
19: return res;
^ Cannot return `res` because property `key` is missing in `Promise` [1] but exists in `A` [2].
References:
[LIB] static/v0.75.0/flowlib/core.js:583: declare class Promise<+R> {
^ [1]
17: const bar = async ():A => {
^ [2]
Solutions tried :
Forcing the type to 'A' of the variable calling await
Casting with any then 'A' didn't seem to solve the error.
Issues Related :
https://github.com/facebook/flow/issues/5294
Purpose of this question:
I am mostly looking for a workaround
This seems to be a simple misunderstanding, but the error message from Flow isn't very useful.
You've declared bar as
const bar = async (): A => {
but async functions always return promises, so it should be
const bar = async (): Promise<A> => {
You can see it here on flow.org/try.
I'm trying to perform a filter on a array of arrays in rxjs. Consider the following:
function guard1(): boolean | Observable<boolean> {}
function guard2(): boolean | Observable<boolean> {}
function guard3(): boolean | Observable<boolean> {}
const routes = [
{ name: 'Foo', canActivate: [guard1, guard2] },
{ name: 'Bar', canActivate: [guard3] },
{ name: 'Moo' }
];
I want to filter the routes array to only routes that return true
from the combination of results of inner array canActivate or if it doesn't have canActivate, I want it to be NOT filtered out.
Lets say guard1 returned true and guard2 returned false, I'd expect route Foo to not be in the filtered list.
I took a stab at this but its not quite doing what I expect:
this.filteredRoutes = forkJoin(of(routes).pipe(
flatMap((route) => route),
filter((route) => route.canActivate !== undefined),
mergeMap((route) =>
of(route).pipe(
mergeMap((r) => r.canActivate),
mergeMap((r) => r()),
map((result) => {
console.log('here', result, route);
return route;
})
)
)));
If I were writing this outside of RXJS, the code might look something like this:
this.filteredRoutes = [];
for (const route of this.routes) {
if (route.canActivate) {
let can = true;
for (const act of route.canActivate) {
let res = inst.canActivate();
if (res.subscribe) {
res = await res.toPromise();
}
can = res;
if (!can) {
break;
}
}
if (can) {
this.filteredRoutes.push(route);
}
} else {
this.filteredRoutes.push(route);
}
}
Thanks!
I'm sure there's other (and likely better ways to handle this, but it works...
from(routes).pipe(
concatMap((route) => {
// handle if nothing is in canActivate
if (!route.canActivate || route.canActivate.length === 0) {
// create an object that has the route and result for filtering
return of({route, result: true})
};
const results = from(route.canActivate).pipe(
// execute the guard
switchMap(guard => {
const result: boolean | Observable<boolean> = guard();
if (result instanceof Observable) {
return result;
} else {
return of(result);
}
}),
// aggregate the guard results for the route
toArray(),
// ensure all results are true
map(results => results.every(r => r)),
// create an object that has the route and result for filtering
map(result => ({route, result})),
);
return results;
}),
// filter out the invalid guards
filter(routeCanActivateResult => routeCanActivateResult.result),
// return just the route
map(routeCanActivateResult => routeCanActivateResult.route),
// turn it back into an array
toArray()
)
// verify it works
.subscribe(routes => routes.forEach(r => console.log(r.name)));
Also, here is a working example in stackblitz.