Problem with calling multiple asynchronous functions in ngOnInit - javascript

Good afternoon,
I am struggling with placing functions in ngOnInit. If I understand what is going wrong correctly, the problem appears to be that when I place asynchronous functions in ngOnInit and the next function depends on the complete execution of the first, if the first has not been completed, then the data I require is still undefined and it will fail.
What I want to do is quite simple, really. ngOnInit populates two arrays by querying my API. One is groups associated with a user, and another is all groups (these come from a DB). It will then populate a third array by comparing these two (allgroups - groupsassociated = groupsavailable).
After this initial populating, I want to be able to do further comparisons between the arrays depending on user interaction. These functions work, but about 10% of the time I will receive an undefined problem which can be cleared up by refreshing the page (sometimes two or three times). This suggests to me that it's a problem of order of operations.
How can I move forward here? Can I make these function calls synchronous? Can I chain them together somehow?
I appreciate any help.
Relevant code below:
NGONINIT AND THREE FUNCTIONS:
ngOnInit(): void {
this.populatePickListUserAssociatedGroups();
this.populateOriginalUserAssociatedGroups();
this.populateAvailableGroups();
}
// METHODS
private populatePickListUserAssociatedGroups = () => {
this.userDataService.getSingleUserAssociatedGroups()
.then(groups => this.pickListUserAssociatedGroups = groups)
.then(groups1 => this.originalUserAssociatedGroups = groups1);
}
private populateOriginalUserAssociatedGroups = () => {
this.userDataService.getSingleUserAssociatedGroups().then(groups => this.originalUserAssociatedGroups);
}
private populateAvailableGroups = () => {
this.userDataService.getAllGroups()
.then(groups => this.allGroups = groups)
.then(groups => groups.filter(i1 => !this.originalUserAssociatedGroups
.some(i2 => i1.id === i2.id)))
.then(groups => this.availableGroups = groups);
}
I have described my main problem, but additionally I have another. For some reason the two arrays: pickListUserAssociatedGroups and originalUserAssociatedGroups always change together. This defeats the purpose. The pick list array represents what the user changes (I'm working with ngPrime pick list) and originalUserAssociatedGroups is supposed to be unchanged and represents what is in the DB so I can later make comparisons between the two.
Hopefully this is clear.
Thanks in advance.

You could achieve that by using rxjs. There is multiple solution to your question but I think the best is to use a forkJoin. Here is how you could achieve that :
ngOnInit() {
forkJoin(
this.populatePickListUserAssociatedGroups(),
this.populateOriginalUserAssociatedGroups(),
).subscribe(finalResult => {
// do your things with finalResult[0] and finalResult[1]
this.populateAvailableGroups();
});
}
populatePickListUserAssociatedGroups(): Observable<any> {
return this._myService.getPickListUserAssociatedGroups();
}
populateOriginalUserAssociatedGroups(): Observable<any> {
return this._myService.getOriginalUserAssociatedGroups();
}
populateAvailableGroups() {
// your code
}
Note that i use observable, not promise. You might need to add a value check in the subscribe to be sure you have the value you need, but the documentation says :
This operator is best used when you have a group of observables and only care about the final emitted value of each.
If your html depends on the data your methods retrieve, you'll have to adapt it, by adding a loader for example, to wait for the subscribe to end since it is asynchronous.
Finally, your service method should return an observable, and you can do it by simply retunring that response :
myServiceMethod(): Observable<any> {
return this._http.get<Whatever>(url);
}
For your other problem, if the changes of the pick list array is made locally in your constructor then you just have to store it in a variable in the forkJoin subscription : this.mypickListArray = finalResult[0] and just work with it.

In case anyone sees the above answer from #Quentin, I thought I would post the code that I ultimately used which worked as intended and to which I am thankful for Quentin's help.
ngOnInit(): void {
forkJoin(
[
this.populateAllGroups(),
this.populateAllUserGroup()
]
)
.subscribe(([x, y]) => {
this.allGroups = x;
this.allUserGroupByUser = y;
this.populateAvailableGroups();
this.populatePickListUserAssociatedGroups();
});
}
Where, for example, the first method looks like this:
private populateAllGroups(): Observable<any> {
return this.userDataService.getAllGroups();
}
And the data service has this method:
public getAllGroups(): Observable<any> {
return this.httpClient.get<Group[]>(`${Configuration.userApiURL}/getallgroups`);
}
The secondary methods are not relevant as they just manipulate the data provided.

Related

Wait for response in angular subscriber

I have seen multiple posts with the same title but none of them is what I need for my use case. I have an array of items, item property can have ('serials','macs','null') I have to call a different API end point for each one and for example inside 'model' there are an array of models which also need to be sent one by one. I know it's not the best way to deal with this but it's a long story and I'm not responsible for this mess of a design .
the service code :
createDevice(data: Device) {
return this.http.post('somewhere', data, {
headers: this.httpHeader,
});
}
component code:
this.sourceDataSet.forEach(item=>{
if(item.serials){
item.serials.forEach(serial=>{
//create it
})
}
else if(item.macs){
item.macs.forEach(serial=>{
//create it
})
}
else{
// create it
}
})
the problem is there are other properties which will get affected by this and I don't want them to.
is there a way to do this.
you can use toPromise() to convert it into a promise and use await.
https://www.learnrxjs.io/learn-rxjs/operators/utility/topromise

Angular - properly nesting Observables and its subscriptions

I am using angularFire2 to extract my data from Firebase as Observable objects. Here is a simplified version of my code with some explanations to it below:
this.af.getObservable(`userChats/${this.userID}/`).subscribe((recentConversations) => {
recentConversations.forEach(conversation => {
this.af.getObservableSortByVar(`allChats/${conversation.conversationID}/`, "lastUpdate").subscribe((conversationData) => {
let userKey, lastMessage, lastMessageText, lastMessageSender, lastMessageDate;
for (var i = 0; conversationData.length > i; i++) {
switch (conversationData[i].key) {
case "messages": {
lastMessage = (conversationData[i][Object.keys(conversationData[i])[Object.keys(conversationData[i]).length - 1]]);
lastMessageText = lastMessage.message;
lastMessageSender = lastMessage.sender;
lastMessageDate = lastMessage.date;
}
case "users": {
userKey = conversationData[i].userKey;
}
}
}
this.recentChats.push(this.createConversationObject("username", userKey, lastMessageSender, lastMessageText, lastMessageDate));
});
});
});
Currently, I am making a call to the database to retrieve a list of all conversations of a user.
I receive an Observable object of all the conversations which I subscribe to since I want to keep the data up-to-date with the database.
I am then iterating through the conversations' Observable. I need to make a new database call for each iterated element(each conversation) in order to obtain information/metadata about it(content, senderID, date of conversation etc). Thus, I result in having two Observables - one nested into the other which both have been subscribed to.
After obtaining the contents/metadata of the conversation from the second observable, I push the metadata obtained, as an Object into an array called "recentChats".
This gets the job done when I execute this whole block of code once(the initial call at the start of the program). However, when the data in the database is modified(the 'userChat' node in the database or the 'allChats' node, or both!) and subscriptions are activated, and I get multiple (repetitive) calls of this whole block of code which floods my array with the same result multiple times.
I get unnecessary calls when I just want to have one single call to refresh the information.
And thus, I can see that my logic and understanding of Observables is not correct. Can someone explain what would be the proper solution of this example above? How can I nest Observable subscriptions without having repetitive (the same) calls?
I think with RxJS you should never have to write your code like that.
However, when the data in the database is modified(the 'userChat' node in the database or the 'allChats' node, or both!) and subscriptions are activated, and I get multiple (repetitive) calls of this whole block of code which floods my array with the same result multiple times.
Each time your outer Observable emits a value, you subscribe to each inner Observable again. That means you have for the same conversations multiple Subscriptions which get executed.
I suggest using operators to have only one Observable and subscribe once
Example (with RxJS 6 syntax, if you are below 5.5 it may look different) (maybe you have to use different operators):
this.af.getObservable(`userChats/${this.userID}/`).pipe(
// we map the array of conversations to an array of the (before inner) observables
map(recentConversations =>
recentConversations.map(conversation =>
this.af.getObservableSortByVar(`allChats/${conversation.conversationID}/`, 'lastUpdate'))),
// combine the observables. will emit a new value of ALL conversation data when one of the conversations changes
switchMap(recentConversations => combineLatest(recentConversations)),
// map each conversation to the conversation object (this is the code you had in your inner subscription)
map(conversations =>
conversations.map(conversationData => {
let userKey, lastMessage, lastMessageText, lastMessageSender, lastMessageDate;
for (let i = 0; conversationData.length > i; i++) {
switch (conversationData[i].key) {
case 'messages': {
lastMessage = (conversationData[i][Object.keys(conversationData[i])[Object.keys(conversationData[i]).length - 1]]);
lastMessageText = lastMessage.message;
lastMessageSender = lastMessage.sender;
lastMessageDate = lastMessage.date;
} // don't you need a "break;" here?
case 'users': {
userKey = conversationData[i].userKey;
}
}
}
return this.createConversationObject('username', userKey, lastMessageSender, lastMessageText, lastMessageDate);
}))
).subscribe(recentChats => this.recentChats = recentChats);

Using data from Observable Angular 2

First, I'd like to thank the community for all the support learners like me get while working with new technologies.
I've been using Angular for a while now, and there's something I still don't quite understand nor have I seen asked elsewhere.
Supposing I have a service that returns an Observable with the data I need, how should I use this data properly, in terms of performance?
I know I can use the async pipe and avoid having to sub/unsub, but this only happens in the template. What if I needed to use the same data in the component as well? Wouldn't subscribing again (from the template with the async pipe and from the component with .subscribe())?
How do I keep the observable up to date? For example, I have a table displaying data from an API. If I click on the 2nd page (pagination), I'd like to make another call and have the observable updated.
I'm sorry if this has been asked before, I personally couldn't find if on Stackoverflow. Thanks for your attention!
If you need the data in the component as well, you can just subscribe to it. BUT maybe you should not (see below)...
it's there that you use the operators, you can combine observables to define a custom data flow:
foo$: Observable < Foo[] > ;
randomClickEvent = new Subject < clickEvent > ();
ngOnInit() {
let initialFetch = this.fooService.getData().share()
this.foo$ = Observable.merge(
initialFetch, // need the initial data
initialFetch.flatMap(foos => {
this.randomClickEvent.switchMap(() => { //listen to click events
return this.fooService.getMore().map((moreFoos: Foo[]) => { //load more foos
return foos.concat(...moreFoos) //initial foos values + new ones
})
})
})
);
}
<span *ngFor="let foo of (foo$|async)">{{foo.name}}</span>
<button (click)="randomClickEvent.next($event)">Load More foos !</button>
Most of people just use simple operators like map(),do(), etc and manage their subscription imperatively, but it is usually better to not subscribe, so you avoid many side effects and some "Ooops I forgot to unsubscribe here". usually you can do everything you need without subscribing.
Observables exist to describe a data flow, nothing more, nothing less. It's a paradigm of functional programming: you don't define how things are done, but what they are. Here, this.foo$ is a combination of the initial fooService.getData() and every fooService.fetchMore() that may occur.

How to create an array from rxjs without completion of the adding sequence

I'm trying to figure out an rxjs way of doing the following:
You have two observables, one onAddObs and onRemoveObs.
let's say onAddObs.next() fires a few times, adding "A", "B", "C".
I would like to then get ["A", "B", "C"].
.toArray requires the observable be completed...yet more could come.
That's the first part. The second part is probably obvious...
I want onRemoveObs to then remove from the final resulting array.
I don't have a plunkr cuz I can't get anything close to doing this...
Thanks in advance!
UPDATE
Based on user3743222's advice, I checked out .scan, which did the job!
If anyone else has trouble with this, I've included an angular2 service which shows a nice way of doing this. The trick is to use .scan and instead of streams of what was added/removed, have streams of functions to add/remove, so you can call them from scan and pass the state.
#Injectable()
export class MyService {
public items: Observable<any>;
private operationStream: Subject<any>;
constructor() {
this.operationStream = Subject.create();
this.items = this.operationStream
// This may look strange, but if you don't start with a function, scan will not run....so we seed it with an operation that does nothing.
.startWith(items => items)
// For every operation that comes down the line, invoke it and pass it the state, and get the new state.
.scan((state, operation:Function) => operation(state), [])
.publishReplay(1).refCount();
this.items.subscribe(x => {
console.log('ITEMS CHANGED TO:', x);
})
}
public add(itemToAdd) {
// create a function which takes state as param, returns new state with itemToAdd appended
let fn = items => items.concat(itemToAdd);
this.operationStream.next(fn);
}
public remove(itemToRemove) {
// create a function which takes state as param, returns new array with itemToRemove filtered out
let fn = items => items.filter(item => item !== itemToRemove);
this.operationStream.next(fn);
}
}
You can refer to the SO question here : How to manage state without using Subject or imperative manipulation in a simple RxJS example?. It deals with the same issue as yours, i.e. two streams to perform operations on an object.
One technique among other, is to use the scan operator and a stream of operations which operates on the state kept in the scan but anyways go have a look at the links, it is very formative. This should allow you to make up some code. If that code does not work the way you want, you can come back and ask a question again here with your sample code.

How to restart or refresh an Observable?

I've got a TypeScript/Angular 2 Observable that works perfectly the first time I call it. However, I'm interested in attaching multiple subscribers to the same observable and somehow refreshing the observable and the attached subscribers. Here's what I've got:
query(): Rx.Observable<any> {
return this.server.get('http://localhost/rawData.json').toRx().concatMap(
result =>
result.json().posts
)
.map((post: any) => {
var refinedPost = new RefinedPost();
refinedPost.Message = post.Message.toLowerCase();
return refinedPost;
}).toArray();
}
Picture that there is a refresh button that when pressed, re-executes this observable and any subscribers that are connected to it get an updated set of data.
How can I accomplish this?
I don't know so much about Angular2 and Typescript, but typescript being a superset of javascript, I made the following hypothesis (let me know if I am wrong) which should make the following javascript code work :
server.get returns a promise (or array or Rx.Observable)
posts is an array of post
You would need to create an observable from the click event on that button, map that click to your GET request and the rest should be more or less as you wrote. I cannot test it, but it should go along those lines:
// get the DOM id for the button
var button = document.getElementById('#xxx');
// create the observable of refined posts
var query$ =
Rx.Observable.fromEvent(button, 'click')
.flapMapLatest(function (ev) {
return this.server.get('http://localhost/rawData.json')
})
.map(function (result){return result.json().posts})
.map(function (posts) {
return posts.map(function(post){
var refinedPost = new RefinedPost();
refinedPost.Message = post.Message.toLowerCase();
return refinedPost;
})
})
.share()
// subscribe to the query$. Its output is an array of refinedPost
// All subscriptions will see the same data (hot source)
var subscriber = query$.subscribe(function(refinedPosts){console.log(refinedPosts)})
Some explanation :
Every click will produce a call to server.get which returns an observable-compatible type (array, promise, or observable). That returned observable is flattened to extract result from that call
Because the user can click many times, and each click generate its flow of data, and we are interested (another hypothesis I make here too) only in the result of the latest click, we use the operator flatMapLatest, which will perform the flatMap only on the observable generated by the latest click
We extract the array of post posts and make an array of refinedPost from it. In the end, every click will produce an array of refinedPost which I assume is what you want
We share this observable as you mention you will have several subscribers, and you want all subscribers to see the same data.
Let me know if my hypotheses are correct and if this worked for you.
In addition I recommend you to have a look at https://gist.github.com/staltz/868e7e9bc2a7b8c1f754
In addition to being a very good reminder of the concepts, it addresses a refresh server call problem very similar to yours.

Categories