i have following code written in rxjs5 and it broke with rxjs6
can some one help me with write in rxjs 6
its failing mergemap receiving groupedObserable which does not have count method and along also filter method does not exist.
list [
{id: '1', type: 't1', name: 'f1', des:'d1', selected: true},
{id: '2', type: 't1', name: 'f2', des:'d2', selected: false},
{id: '3', type: 't1', name: 'f11', des:'d11', selected: false},
{id: '4', type: 't1', name: 'f22', des:'d22', selected: true},
]
Observable.from(list)
.filter(a => a.name != null)
.groupBy(i => i.type)
.mergeMap(list => {
let count = list.count;
let selectedCount = 0;
list.filter( f => f.selected).count.subscribe(c => selectedCount = c)
return count.map(count => {
{
key: list.key,
totalCount: count,
selected: selectedCount
}
}
}).reduce((x, y) => {
x.isValid = x.selectedCount > 0
return x;
}).subscribe(r => {
console.log(r + 'any item selected')
}
)
when i tried to write in rxjs6 only progress i was able to made till here
thanks in advance.
from(list)
.pipe(
filter( s=> s.name != null) ,
groupBy(i => i.type),
mergeMap( (value, index) => {
value.count // that's where it starts to fail
}
))
The equivalent rxjs6 code should be like this:
from(list)
.pipe(
filter(a => a.name != null),
groupBy(i => i.type),
mergeMap((p) => {
return p.pipe(
filter(f => f.selected),
count(),
mergeMap(c => {
return p.pipe(
count(),
map(totalCount => {
return {
key: p.key,
totalCount: totalCount,
selected: c
};
})
);
})
);
}),
reduce((x, y) => {
//please adjust your code here as i could not see isValid on x
x.isValid = x.selectedCount > 0;
return x;
})
).subscribe(r => {
console.log(r + 'any item selected')
}
)
Hope it gives an idea of how to proceed.
Related
I want to return Observable from GetEmployeeByID function that has a matching productName from allProducts$:
allProducts$ = this.state$.pipe(
map(x => x.allProducts as IProducts[] || [] as IProducts[]),
distinctUntilChanged());
GetEmployeeByID(Id: number): Observable<IEmployees> {
return this.allEmployees$.pipe(
map(res => <IEmployees>res.find(data => data.id === Id) ?? { id: -1, name: "N/A", email: "N/A", gender: "N/A", productid: -1, productName: "N/A" } as IEmployees),
switchMap((Employee: IEmployees) => {
return this.allProducts$.subscribe(allProducts => {
return of({
...Employee,
productName: allProducts.find(Product => Product.productid == Employee.productid)?.name
}) as IEmployees
})
})
)
}
Instead of subscribing allProducts$ inside switchMap, pipe through it, do what you need inside the pipe, and return its resulting observable. See the code below for context.
class Foo {
allProducts$ = this.state$.pipe(
map(x => <IProducts[]> (x.allProducts ?? [])),
distinctUntilChanged()
);
public GetEmployeeByID(Id: number): Observable <IEmployees> {
return this.allEmployees$.pipe(
map(res => <IEmployees> res.find(data => data.id === Id) ?? {
id: -1,
name: "N/A",
email: "N/A",
gender: "N/A",
productid: -1,
productName: "N/A"
}),
switchMap((Employee: IEmployees) => {
return this.allProducts$.pipe( // 👈 pipe through `allProducts$`
map((allProducts) => <IEmployees> ({
...Employee,
productName: allProducts.find(Product => Product.productid == Employee.productid)?.name
}))
)
})
)
}
}
Using Angular Rxjs and ngrx
I have an action that dispatch 4 API and I am doing the following =>
#Effect()
getAllModels$ = this.actions$.pipe(
ofType<featureActions.GetAllModelsRequest>(featureActions.ActionTypes.GetAllModelsRequest),
switchMap((action) =>
forkJoin([
this.dataService.GetAllModelFromServer(),
this.dataService.GetAllModelFromHost(),
this.dataService.GetAllModelFromCache(),
this.dataService.GetAllModelFromPreference(),
]).pipe(
map(
([server, host, cache, preference]) =>
new featureActions.GetAllModelsSuccess({
//...
})
),
catchError((error: HttpErrorResponse) => {
return of(new featureActions.GetAllModelsFailed({ error: error.message }));
})
)
)
);
The problem is, when one of those API fail, everything fail and I am in fail action. all the data that got retrieved (before the one endpoint that failed) is lost.
Is there a way to get the data retrieved in the catchError or the only solution is to chain the api one after the other ?
You can write your own implementation of forkJoin. Here is a simple example sourced from the original (https://github.com/ReactiveX/rxjs/blob/master/src/internal/observable/forkJoin.ts):
export function forkJoin2(...args: any[]): Observable<any> {
const resultSelector = popResultSelector(args);
const { args: sources, keys } = argsArgArrayOrObject(args);
if (resultSelector) {
// deprecated path.
return forkJoinInternal(sources, keys).pipe(map((values: any[]) => resultSelector!(...values)));
}
return forkJoinInternal(sources, keys);
}
function forkJoinInternal(sources: ObservableInput<any>[], keys: string[] | null): Observable<any> {
return new Observable((subscriber) => {
const len = sources.length;
if (len === 0) {
subscriber.complete();
return;
}
const values = new Array(len);
let completed = 0;
let emitted = 0;
for (let sourceIndex = 0; sourceIndex < len; sourceIndex++) {
const source = innerFrom(sources[sourceIndex]);
let hasValue = false;
subscriber.add(
source.subscribe({
next: (value) => {
if (!hasValue) {
hasValue = true;
emitted++;
}
values[sourceIndex] = value;
},
error: (err) => { return subscriber.error({ error: err, values }) },
complete: () => {
completed++;
if (completed === len || !hasValue) {
if (emitted === len) {
subscriber.next(keys ? keys.reduce((result, key, i) => (((result as any)[key] = values[i]), result), {}) : values);
}
subscriber.complete();
}
},
})
);
}
});
}
Notice, when an error occurs, you are returning the error along with the values:
error: (err) => { return subscriber.error({ error: err, values }) }
I went with this solution found here : https://medium.com/better-programming/rxjs-error-handling-with-forkjoin-3d4027df70fc
#Effect()
getAllModels$ = this.actions$.pipe(
ofType<featureActions.GetAllModelsRequest>(featureActions.ActionTypes.GetAllModelsRequest),
switchMap((action) =>
forkJoin([
this.dataService.GetAllModelFromServer().pipe(catchError(() => of({ data: [] }))),
this.dataService.GetAllModelFromHost().pipe(catchError(() => of({ data: [] }))),
this.dataService.GetAllModelFromCache().pipe(catchError(() => of({ data: [] }))),
this.dataService.GetAllModelFromPreference().pipe(catchError(() => of({ data: [] }))),
]).pipe(
map(
([server, host, cache, preference]) =>
new featureActions.GetAllModelsSuccess({
//...
})
),
catchError((error: HttpErrorResponse) => {
return of(new featureActions.GetAllModelsFailed({ error: error.message }));
})
)
)
);
I have an operator that does some recursive operations on a child property of the source. What do I do to merge the child property back into the source after I'm done the recursive operation?
const state = {
posts: [
{id: 3, title: 't1', userId: 1},
],
index: 0,
config: {
previousBufferSize: 1,
nextBufferSize: 1,
}
};
const source = new BehaviorSubject(state);
const generatePreviousPosts$ = (posts) => {
return Observable.create(observer => {
getPost(posts[0].id - 1)
.then(previousPost => {
observer.next([previousPost, ...posts]);
});
});
};
const previousBuffer$ = source.pipe(
pluck('posts'),
expand(generatePreviousPosts$),
tap(console.log),
// What do I do to merge post back in the state so I could use takeWhile?
takeWhile(state => {
const {posts, config, index} = state;
return posts.length <= config.previousBufferSize - index + posts.length &&
posts[0].id != null;
})
);
One way to do it is to use mergeMap, but I feel like there could be a more elegant solution to this problem.
const previousBuffer$ = source.pipe(
mergeMap(
state => (of(state.posts)
.pipe(expand(generatePreviousPosts$))),
(state, posts) => ({...state, posts})),
takeWhile(state => {
const {posts, config, index} = state;
return posts.length <= config.previousBufferSize - index + posts.length &&
posts[0].id != null;
})
);
A much more elegant solution given by https://github.com/Dorus on the rxjs gitter.
const shouldGetPost = ({posts, config, index}) => posts.length <= config.previousBufferSize - index + posts.length
&& posts[0].id != null
const generatePreviousPosts = ({posts, config, index}) => !shouldGetPost({posts, config, index}) ? EMPTY :
from(getPost(posts[0].id - 1)).pipe(
map(previousPost => ({[previousPost, ...posts], config, index}))
)
const previousBuffer$ = source.pipe(
expand(generatePreviousPosts)
);
Problem: Game: So I have some ships that can arrive to many planets. If the 2 ships arrive at the same time on the new planet can lead to the same process of changing ownership twice. This process is asynchronous and should only happen once per planet ownership change.
To fix this I want split the stream of ships by planet id so each stream will be for only one planet. Now the tricky part is that each ship should only be processed after the previous one has been processed.
Ships$
Split by planet id
planet id1: process in sequence
planet id2: process in sequence
...
Here is some code that will show how it should behave.
const ships = [
{
id: 1,
planetId: 1,
},
{
id: 2,
planetId: 1,
},
{
id: 3,
planetId: 2,
},
// ... never finishes
]
// the source observable never finishes
const source$ = interval(1000).pipe(
take(ships.length),
map(i => ships[i]),
)
const createSubject = (ship) => {
// Doesn't need to be a subject, but needs to emit new items after a bit of time based on some other requests.
console.log(`>>>`, ship.id);
const subject = new Subject();
setTimeout(() => {
subject.next(ship.id + ' a' + new Date());
}, 1000);
setTimeout(() => {
subject.next(ship.id + ' b' + new Date());
subject.complete();
}, 2000);
return subject.asObservable();
}
// The result should be the following (t, is the time in seconds, t3, is time after 3 seconds)
// t0: >>> 1
// t0: >>> 3
// t1: 1 a
// t1: 2 a
// t2: 1 b
// t2: 2 b
// t2: >>> 2 (note that the second ship didn't call the createSubject until the first finished)
// t3: 1 a
// t4: 1 2
Solution (with a lot of help from A.Winnen and some figuring out)
Run it here: https://stackblitz.com/edit/angular-8zopfk?file=src/app/app.component.ts
const ships = [
{
id: 1,
planetId: 1,
},
{
id: 2,
planetId: 1,
},
{
id: 3,
planetId: 2,
}
];
const createSubject = (ship) => {
console.log(ship.id + ' a')
const subject = new Subject();
setTimeout(() => {
//subject.next(ship.id + ' b');
}, 500);//
setTimeout(() => {
subject.next(ship.id + ' c');
subject.complete();//
}, 1000);
return subject.asObservable();
}
let x = 0;
interval(10).pipe(//
take(ships.length),
map(i => ships[i]),
groupBy(s => s.planetId),
mergeMap(group$ => {//
x++
return group$.pipe(
tap(i => console.log('x', i, x)),
concatMap(createSubject)
)
}),
).subscribe(res => console.log('finish', res), undefined, () => console.log("completed"))
How can this be done in rxjs?
Code:
const shipArriveAction$ = action$.pipe<AppAction>(
ofType(ShipActions.arrive),
groupBy(action => action.payload.ship.toPlanetId),
mergeMap((shipByPlanet$: Observable<ShipActions.Arrive>) => {
return shipByPlanet$.pipe(
groupBy(action => action.payload.ship.id),
mergeMap((planet$) => {
return planet$.pipe(
concatMap((action) => {
console.log(`>>>concat`, new Date(), action);
// this code should be called in sequence for each ship with the same planet. I don't need only the results to be in order, but also this to be called in sequence.
const subject = new Subject();
const pushAction: PushAction = (pushedAction) => {
subject.next(pushedAction);
};
onShipArriveAction(state$.value, action, pushAction).then(() => {
subject.complete();
});
return subject.asObservable();
}),
)
})
);
)
;
The code from A.Winnen is very close, but only works with a source observable that is finished, not continuous:
const ships = [
{
id: 1,
planetId: 1,
},
{
id: 2,
planetId: 1,
},
{
id: 3,
planetId: 2,
}
];
const createSubject = (ship) => {
console.log(ship.id + ' a')
const subject = new Subject();
setTimeout(() => {
subject.next(ship.id + ' b');
}, 1000);//
setTimeout(() => {
subject.next(ship.id + ' c');
subject.complete();//
}, 2000);
return subject.asObservable().pipe(
finalize(null)
);
}
interval(1000).pipe(
take(ships.length),
tap(console.log),
map(i => ships[i]),
groupBy(s => s.planetId),
mergeMap(group => group.pipe(toArray())),
mergeMap(group => from(group).pipe(
concatMap(createSubject)
))
).subscribe(res => console.log(res), undefined, () => console.log("completed"))
you can use a combination of groupBy and mergeMap to achieve your goal.
from(ships).pipe(
groupBy(ship => ship.planetId),
mergeMap(planetGroup => planetGroup.pipe(
concatMap(ship => {
// do real processing in this step
return of(`planetGroup: ${planetGroup.key} - processed ${ship.ship}`);
})
))
).subscribe(result => console.log(result));
I made a simple example: https://stackblitz.com/edit/angular-6etaja?file=src%2Fapp%2Fapp.component.ts
EDIT:
updated blitzstack: https://stackblitz.com/edit/angular-y7znvk
I have a bit of code that looks like this:
function getEvensSquared(array) {
return rxjs.of(array).pipe(
rxjs.operators.flatMap(array => {
return rxjs.from(
array.filter(n => n % 2 === 0)
);
}),
rxjs.operators.switchMap(n => {
return rxjs.of(n * n);
}),
rxjs.operators.scan((acc, cur) => {
acc.push(cur);
return acc;
}, [])
);
}
getEvensSquared([1, 2, 3, 4]).subscribe(v => {
console.log("1,2,3,4");
console.log(v)
});
getEvensSquared([1, 3, 5]).subscribe(v => {
console.log("1,3,5"); //never prints
console.log(v)
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.2.2/rxjs.umd.min.js"></script>
Essentially it is:
Get a stream of arrays of ObjectA.
Convert the array in to a stream of filtered ObjectA.
Convert the stream of ObjectA into a stream of ObjectB.
Accumulate the stream of ObjectB into a stream of arrays of ObjectB.
The problem is - if there are no approved bookings, the flatMap operator never emits. Is there a way to default in this case?
I tried putting defaultIfEmpty([]) after the scan - but that didn't work.
Positing this as an answer - because it works for the example I've given - defaultIfEmpty does work in this scenario.
function getEvensSquared(array) {
return rxjs.of(array).pipe(
rxjs.operators.flatMap(array => {
return rxjs.from(
array.filter(n => n % 2 === 0)
);
}),
rxjs.operators.switchMap(n => {
return rxjs.of(n * n);
}),
rxjs.operators.scan((acc, cur) => {
acc.push(cur);
return acc;
}, []),
rxjs.operators.defaultIfEmpty([])
);
}
getEvensSquared([1, 2, 3, 4]).subscribe(v => {
console.log("1,2,3,4");
console.log(v)
});
getEvensSquared([1, 3, 5]).subscribe(v => {
console.log("1,3,5"); //works
console.log(v)
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.2.2/rxjs.umd.min.js"></script>