Angular rxjs async array undefined - javascript

There is a a newData array and rxjs forkJoin operator with two methods.
I'm trying to populate the array within getNewData() in order to use it in forkJoin subscription, but it's still undefined. Which would be the appropriate way to wait for newData array to be populated in getNewData() in order to use it in forkJoin subscription?
newData = [];
forkJoin(
this.method1();
this.method2()
).subscribe({ data1, data2 }) => {
const filteredData = data1.filter(item => item.id === model.id);
this.getNewData(filteredData);
console.log(this.newData) => undefined
// wait for this.newData?
}
// Observable
getNewData(filteredData) {
return this.API('GET', `data/${filteredData.id}`).pipe(map((resp: any) => {
this.newData = resp;
}));
}

You're attempting nested subscription which is discouraged.
If the calls are entirely independent of each other, you could append this.API('GET', 'data') call as third argument to the forkJoin function.
import { forkJoin } from 'rxjs';
newData = [];
forkJoin(
this.method1(),
this.method2(),
this.API('GET', `data`)
).subscribe([ data1, data2, newData ]) => {
const filteredData = data1.filter(item => item.id === model.id);
this.newData = newData;
console.log(this.newData);
}
Or if the API call somehow depends on the data from the first two methods, then you could use one of the RxJS higher order mapping operators like switchMap.
import { forkJoin } from 'rxjs';
import { swithcMap } from 'rxjs/operators';
newData = [];
forkJoin(
this.method1(),
this.method2()
).pipe(
switchMap(([data1, data2]) => {
const filteredData = data1.filter(item => item.id === model.id);
return this.getNewData(filteredData);
})
).subscribe(newData => {
this.newData = newData;
console.log(this.newData);
}
getNewData (filteredData): Observable<any> {
return this.API('GET', `data/${filteredData.id}`).pipe(
map((resp: any) => {
this.newData = resp;
})
);
}
Update (accd. to OP's update):
You wish to use the output from forkJoin inside another HTTP call. Then you'd need to switchMap operator as shown.
(Unrelated to the issue) Please try to provide notes like this when you update something in the original post. It makes it easier to understand your intent.

Use combineLatest from RxJS and use filter(), tap() to check for the data
combineLatest(
this.method1(),
this.method2(),
this.getNewData()
).pipe(
filter(data => !!data),
tap(data => console.log(data))
catchError(error => console.log(error))
).subscribe(
[dataFromMethod1, dataFromMethod2, dataFromMethod3] => {
// This will subscribe only if all the observables return data
// Otherwise it will go to catchError
})
// Filter will check if the data is present or not.
// Tap will return the data before subscribing
// catchError ==> Subscription errors are caught in this catchError

Simply add method getNewData to forkjoin as third argument, and you will have newData inside your sub
forkJoin(
this.method1();
this.method2(),
this.getNewData()
).subscribe([ data1, data2 , newData]) => {
const filteredData = data1.filter(item => item.id === model.id);
}
// Observable
getNewData() {
return this.API('GET', `data`)
}
Why you changed your question?
now our answers are not correct - do not do so
UPDATED ANSWER:
forkJoin(
this.method1(),
this.method2()
)
.pipe(
map(([data1, data2]) => {
const filterData = [data1, data2]; // here will be some filtereing
return filterData;
}),
mergeMap(filteredData => {
// here you can append your filterData
return this.getNewData(filteredData);
})
)
.subscribe(console.log);
getNewData(filterData) {
return this.API('GET', filterData)
}
DEMO: https://stackblitz.com/edit/typescript-j7rrwl?file=index.ts

Related

How to return outer observable and not inner in a High-order observables

Lets clarify the problem with the following code:
this.rates$ = this._glbRateService.getRates(params); // 1
this.rates$.pipe(
mergeMap(rates => {
const priceByRates: Observable<any>[] = rates.map(rate => {
const paramsRatingItemProduct = {
idItem: product.idItem,
idRate: rate.idRate
};
return this._glbRatingItemProduct.getPrice(paramsRatingItemProduct); // 2
});
return priceByRates;
})
).subscribe(response => {
console.log(response); // 3
});
In that code:
I get rates from server
For every rate, I get prices (map)
My console.log returns the value from the inner subscription (this._glbRatingItemProduct.getPr...)
And what I want is to do logic with the mapping values and the inner subscription.
Something like this:
this.rates$ = this._glbRateService.getRates(params);
this.rates$.pipe(
mergeMap(rates => {
const priceByRates: Observable<any>[] = rates.map(rate => {
const paramsRatingItemProduct = {
idItem: product.idItem,
idRate: rate.idRate
};
return this._glbRatingItemProduct.getPrice(paramsRatingItemProduct);
// WITH THE SUBSCRIPTION OF THIS RETURN I WANT TO MAKE LOGIC
// WITH rates.map, and then return rates, NOT THE INNER SUBSCRIPTION
});
return priceByRates;
})
).subscribe(response => {
console.log(response);
});
You first need to execute the inner observable array first with maybe forkJoin
then run your mapping function with the array
mergeMap(rates => {
const priceByRates: Observable<any>[] = rates.map(rate => {
const paramsRatingItemProduct = {
idItem: product.idItem,
idRate: rate.idRate
};
return this._glbRatingItemProduct.getPrice(paramsRatingItemProduct);
});
return forkJoin(...priceByRates).pipe((values)=>values.map....your logic ));
})
https://www.learnrxjs.io/learn-rxjs/operators/combination/forkjoin
It's sometimes helpful to separate out the logic of mapping and flattening higher-order observables. Here it should be a bit clearer that map() returns an array of observables and forkJoin() joins all those observables into one stream.
this.rates$ = this._glbRateService.getRates(params);
this.rates$.pipe(
map(rates => rates.map(
rate => this._glbRatingItemProduct.getPrice({
idItem: product.idItem,
idRate: rate.idRate
})
),
mergeMap(priceByRates => forkJoin(priceByRates))
).subscribe(console.log);
On the other hand, forkJoin() only emits once all source observables complete. If you don't need all the responses together, you keep your source streams de-coupled with a simpler merge(). Only one line needs to change:
mergeMap(priceByRates => merge(...priceByRates))
The thing to remember is that mergeMap expects a single stream to be returned. It will convert an array into a stream of values. So mergeMap(num => [10,9,8,7,num]) doesn't map num into an array, it creates a new stream that will emit those numbers one at a time.
That's why mergeMap(_ => val : Observable[]) will just emit each observable, (as a higher order observable) one at a time.
With this knowledge, you can actually change your stream to merge without using the static merge function above. That could look like this:
this.rates$ = this._glbRateService.getRates(params);
this.rates$.pipe(
mergeMap(rates => rates.map(
rate => this._glbRatingItemProduct.getPrice({
idItem: product.idItem,
idRate: rate.idRate
})
),
mergeAll()
).subscribe(console.log);
mergeAll() will take each higher-order observable as it arrives and subscribe+merge their output.

How to convert an observable to Promise after pipe()

I have an async function that fetch data. In order to manipulate the data returned, I used from() to convert the Promise to an observable and use pipe() to manipulate the data. Is is possible to convert it back to Promise after pipe()? I have tried the following but it didn't work:
getOrder() {
return from(asyncFunctionToGetOrder())
.pipe(map(data) =>
//Processing data here
return data;
))
.toPromise(); //This won't work
}
I don't know why you want to return promise, why cannot you convert to promise while fetching data, see this:
this.getOrder()
.toPromise()
.then((response:any) => {
this.records = response;
}).catch(error => {
console.log(error)
}).finally(() => {
// code to cleanup
});
Without Observable:
getOrder() {
return asyncFunctionToGetOrder().then(
(data) => {
// Processing data here
return Promise.resolve(data);
}
);
}
It should be working, though.
Remember that toPromise only return when the observable completes.
Also that if your promise rejects, you need to catch error.
Put together an example for you: https://stackblitz.com/edit/rxjs-4n3y41
import { of, from } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
const fetchCall = () => {
return Promise.resolve({a: 1, b: 2});
}
const problematicCall = () => {
return Promise.reject('ERROR')
}
const transformedPromise = from(fetchCall())
.pipe(map(data => data.a))
.toPromise()
const problematicTransformed = from(problematicCall())
.pipe(catchError(data => of(data)))
.toPromise()
transformedPromise.then((a) => console.log('Good boy', a));
problematicTransformed.then((a) => console.log('Bad boy', a));

Wait end of two subscribe to make an operation

I have two subscribe like this :
this.birthdays = await this.birthdaySP.getBirthdays();
this.birthdays.subscribe(groups => {
const allBirthdayT = [];
groups.map(c => {
allBirthdayT.push({
key: c.payload.key,
...c.payload.val()
})
})
console.log(allBirthdayT);
});
this.birthdaysInGroups = await this.birthdaySP.getBirthdaysInGroups();
this.birthdaysInGroups.subscribe(groups => {
const allBirthdayB = [];
groups.map(c => {
c.birthdays.subscribe(d => {
d.map(e => {
allBirthdayB.push(e);
})
})
})
console.log(allBirthdayB);
});
I would like to wait the end of this two subscribes to compare allBirthdayB and allBirthdayT arrays (i receive datas in two console.log).
this.birthdaySP.getBirthdays() and this.birthdaySP.getBirthdaysInGroups() are two observable that receive data from firebase.
The first Observable is like that :
async getBirthdays() {
const user = await this.authSP.getUserInfo();
return this.angularFire.list('birthdays', ref => ref.orderByChild('creator_user_id').equalTo(user.uid)).snapshotChanges();
}
I try with forkJoin but i don't know how i can use it to solve this problem
Any tips?
You can use the combineLatest() function.
Example:
combineLatest(observable1$, observable2$)
.subscribe(([observable1, observable2]) => {
console.log(observable1, observable2);
});

Is there a way to use a observable returning function for each element of another observable array?

I get an Observable<Group[]> from my Firebase collection.
In this Group class is an id which I wanna use to retrieve another dataset array from Firebase, which would be messages for each unique group Observable<Message[]>.(each group has its own chat: Message[])
And it want to return an observable which hold an array of a new Type:
return { ...group, messages: Message[] } as GroupWithMessages
the final goal should be Observable<GroupWithMessages[]>
getGroupWithChat(): Observable<GroupWithMessages[]> {
const groupColl = this.getGroups(); // Observable<Group[]>
const messages = groupColl.pipe(
map(groups => {
return groups.map(meet => {
const messages = this.getMessagesFor(group.uid);
return { messages:messages, ...group} as GroupWithMessages
});
})
);
return messages;
}
}
and here the Message function
getMessagesFor(id: string): Observable<Message[]> {
return this.afs.collection<Message>(`meets/${id} /messages`).valueChanges();
}
sadly that doesnt work because when i create the new Obj I cannot bind messages:messages because messages ist vom typ Observable<Message[]>
I hope that cleares things
UPDATE:
my main problem now comes down to this:
getGroupsWithMessages() {
this.getJoinedGroups()
.pipe(
mergeMap(groups =>
from(groups).pipe(
mergeMap(group => {
return this.getMessagesFor(group.uid).pipe(
map(messages => {
return { ...group, messages } as GroupIdMess;
})
);
}),
tap(x => console.log('reaching here: ', x)),
toArray(),
tap(x => console.log('not reaching here = completed: ', x))
)
),
tap(x => console.log('not reaching here: ', x))
)
.subscribe(x => console.log('not reaching here: ', x));
}
when i call that function my console.log is as follows:
Not sure if I follow what you're doing here but the logic look like you'd want:
getGroupWithChat() {
return this.getGroups.pipe(map(groups=> {
return groups.map(group => this.getMessagesFor(group.uid));
})).subscribe(); // trigger "hot" observable
}
Let me know if I can help further after you clarify.
UPDATE:
So it looks like you need to get the UID of the group before making the call to get the GroupMessages[]?
get Group: Observable
call getMessagesFor(Group.uid)
this example gets groups result$ then
concatMap uses groups result$ to make the messages query
this.getGroups().pipe(
concatMap((group: Group) => this.getMessagesFor(group.uid))
).subscribe((messages: GroupWithMessages[]) => {
console.log(messages);
});
You may still want to map them together but it seems like you know how to do that. concatMap waits for the first to finish, then makes the second call which you need.
Is this closer?
Use forkJoin to wait for messages to be received for all groups. Then map the result of forkJoin to an array of GroupWithMessages like this -
getGroupWithChat(): Observable<GroupWithMessages[]> {
return this.getGroups()
.pipe(
switchMap(groups => {
const messagesForAllGroups$ = groups.map(group => this.getMessagesFor(group.uid));
return forkJoin(messagesForAllGroups$)
.pipe(
map(joined => {
//joined has response like -
//[messagesArrayForGroup0, messagesArrayForGroup1, messagesArrayForGroup2....];
const messagesByGroup = Array<GroupWithMessages>();
groups.forEach((group, index) => {
//assuming that GroupWithMessages has group and messages properties.
const gm = new GroupWithMessages();
gm.group = group;
gm.messages = joined[index];
messagesByGroup.push(gm);
});
return messagesByGroup;
})
)
})
)
}
I usually do that by splitting Observable<any[]> to Observable<any> and then mergeMap the results to inner Observable.
Something like this should work:
getMessagesFor(id: string): Observable<number> {
return of(1);
}
getGroups(): Observable<string[]> {
return of(["1", "2"]);
}
getGroupWithChat() {
this.getGroups().pipe(
mergeMap(groups => from(groups)), // Split the stream into individual group elements instead of an array
mergeMap(group => {
return this.getMessagesFor(group).pipe(
map(messages => {
return Object.assign(group, messages);
})
);
})
);
}
Edit:
Consider BehaviorSubject. It doesn't complete at all:
const behSub: BehaviorSubject<number[]> = new BehaviorSubject([1, 2, 3]);
setTimeout(() => {
behSub.next([4, 5, 6]);
}, 5000);
behSub
.pipe(
mergeMap(arr =>
from(arr).pipe(
tap(), // Do something with individual items, like mergeMap to messages
toArray() // Go back to array
)
)
)
.subscribe(console.log, null, () => {
console.log('Complete');
});

Http get call returns too many results. ::JavaScript

I have a function:
private getValues() {
this._values = [];
this._watchlistElements.map(v =>
this.http.get('http://localhost/getValue/' + v.xid)
.subscribe(res => {
this._values.push(res.json());
console.log(this._values.length);
}))
};
After calling that function, Im getting a result which looks like:
My question is how to check the actual length of the _values variable? This solution works but as you can see it returns a result with every iteration, like in a loop. How to make it to return just one, final result? In this case --> the length should be just 7.
Capturing each call in a Promise, then waiting for all is a good approach:
Promise.all(this._watchlistElements
.map(v => new Promise(resolve => this.http.get(...).subscribe(res => resolve(res.json())))))
.then(arrayWithResultFromEachCall => console.log(arrayWithResultFromEachCall.length))
Had the same problem. Just used observables from rxjs.
First of all import the observables into your component:
import {Observable} from 'rxjs/Observable';
Then, the fixed function:
private getValues() {
this._values = [];
Observable.forkJoin(
this._watchlistElements.map(v => {
return this.http.get('http://localhost/getValue/' + v.xid)
.map(res => res.json());
})
).subscribe(v => {
this._values = v;
console.log(this._values.length);
});
};
The result => 7.

Categories