Wait for response in angular subscriber - javascript

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

Related

Problem with calling multiple asynchronous functions in ngOnInit

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.

Angular Http subscribe - cannot assign to variable in component

I want to retrieve data from api and assign it to some value inside the angular component. In subscribe I'm trying to assign the data to loggedUser and then call function inside this subscribe to navigate to another component with this received object. Unfortunately I got the error : The requested path contains undefined segment at index 1. I want to have this object set outside the subscribe too. How can I achieve this?
logIn() {
this.portfolioAppService.logIn(this.loggingUser).subscribe((data) => {
this.loggedUser = data;
console.log(this.loggedUser);
console.log(data);
this.navigateToProfile(this.loggedUser.Id);
});
}
navigateToProfile(id: number) {
this.router.navigate(['/profile', id]);
}
console output
You are using an incorrectly named property when calling navigateToProfile.
From your console output, I can see that the data object in the subscribe looks like this:
{
id: 35,
// ..
}
But you are calling the function like this:
this.navigateToProfile(this.loggedUser.Id);
Instead, use the property id (lower case)
this.navigateToProfile(this.loggedUser.id);
To narrow this problem down in the future, try being more specific in your testing. Humans are good at seeing what they want to see and will assume the problem is more complicated than it is. If you had tried console.log(this.loggedUser.Id), you would have seen the result undefined, and worked out the problem yourself.

angular angularfire2 simple join

i'm trying to create a post-comment relationship​​ where the a user can write a post and others users can comment on the post.
I can show the posts but when in trying to do the join for displaying the comments that belongs to the post i cant..
below is my db schema
i was thinking that first i need to get the key from the posts node and then move to comments and somehow get the comments of each post..
and use it in *ngfor inside the ngfor of the post?
i was trying something like
findAllComments(){
this.db.list('posts', { preserveSnapshot: true})
.subscribe(snapshots=>{
snapshots.forEach(snapshot => {
return this.db.list(`comments/${snapshot.key}`)
});
});
}
but this returns void of course:
When I console.log:
findAllComments(){
this.db.list('/posts', { preserveSnapshot: true})
.subscribe(snapshots=>{
snapshots.forEach(snapshot => {
const kapa = this.db.list(`comments/${snapshot.key}`).do(console.log)
kapa.subscribe();
});
});
}
I get in console this
I'm not sure if my thinking on this is right.
I'm confused because I am new in angular and firebase.
You aren't returning a subset of posts (you're querying on all posts) so there's no need to have a join of any sort here. You can just query for all comments:
findAllComments(){
// {preserveSnapshot: true} is deprecated
return this.db.list('/comments').snapshotChanges();
}
Assuming you actually want to retrieve a subset of comments (not what your example depicts), you could do something like this:
this.replies = db.list('AngularFire/joins/messages').snapshotChanges().map(snapshots => {
console.log('snapshots', snapshots);
return snapshots.map(ss => {
return db.list(`AngularFire/joins/replies/${ss.key}`).valueChanges();
});
});
There is a complete working example of the latter here.
I guess in the first part, you are not subscribing to the comments list. As there is no subscription to the comments, the request to the get the list of comments from firebase will not be fired and hence you don't see any comments.
In the second part, as you are subscribing to the comments list, you are seeing them.
In cases like these, where you want to fetch something based on a previous request, you could use switch/concat/merge Maps. Hope this helps

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.

Publishing/Subscribing same collection based on different Session value

I want to publish and subscribe subset of same collection based on different route. Here is what I have
In /server/publish.js
Meteor.publish("questions", function() {
return Questions.find({});
});
Meteor.publish("questionSummaryByUser", function(userId) {
var q = Questions.find({userId : userId});
return q;
});
In /client/main.js
Deps.autorun(function() {
Meteor.subscribe("questions");
});
Deps.autorun(function () {
Meteor.subscribe("questionSummaryByUser", Session.get("selectedUserId"));
});
I am using the router package (https://github.com/tmeasday/meteor-router). They way i want the app to work is when i go to "/questions" i want to list all the questions by all the users and when i visit "/users/:user_id/questions", I want to list questions only by specific user. For this I have setup the "/users/:user_id/questions" route to set the userid in "selectedUserId" session (which i am also using in "questionSummaryByUser" publish method.
However when i see the list of questions in "/users/:user_id/questions" I get all the questions irrespective of the user_id.
I read here that the collections are merged at client side, but still could not figure a solution for the above mentioned scenario.
Note that I just started with Meteor, so do not know in and outs of it.
Thanks in advance.
The good practice is to filter the collection data in the place where you use it, not rely of the subset you get by subscribe. That way you can be sure that the data you get is the same you want to display, even when you add further subscriptions to the same collection. Imagine if later you'd like to display, for example, a sidebar with top 10 questions from all users. Then you'd have to fetch those as well, and if you have a place when you display all subscribed data, you'll get a mess of every function.
So, in the template where you want to display user's questions, do
Template.mine.questions = function() {
return Questions.find({userId: Meteor.userId()});
};
Then you won't even need the separate questionSummaryByUser channel.
To filter data in the subscription, you have several options. Whichever you choose, keep in mind that subscription is not the place in which you choose the data to be displayed. This should always be filtered as above.
Option 1
Keep everything in a single parametrized channel.
Meteor.publish('questions', function(options) {
if(options.filterByUser) {
return Questions.find({userId: options.userId});
} else {
return Questions.find({});
}
});
Option 2
Make all channel return data only when it's needed.
Meteor.publish('allQuestions', function(necessary) {
if(!necessary) return [];
return Questions.find({});
});
Meteor.publish('questionSummaryByUser', function(userId) {
return Questions.find({userId : userId});
});
Option 3
Manually turn off subcriptions in the client. This is probably an overkill in this case, it requires some unnecessary work.
var allQuestionsHandle = Meteor.subscribe('allQuestions');
...
allQuestionsHandle.stop();

Categories