Tracker.autorun and subscriptions from array - meteor

I registered three Tracker.autorun functions with collection from array: ['tags', 'allUsers', 'userGroups']
formObj.collections.forEach(collection => {
Tracker.autorun(() => {
const handle = Meteor.subscribe(collection);
if (handle.ready()) {
dispatch(collectionIsReady(formObj, collection));
console.log(collection);
if (_.isEqual(formObj.collections, formObj.loadedCollections)) {
dispatch(collectionsAreReady(formObj));
dispatch(formIsReady(formObj));
}
}
});
});
If one of these collections changed the Tracker.autorun function is executed but only with the collection name of the last array item (console output: "userGroups"). What should i do to get the correct collection name?
thanks for your help
Edit:
There is no need to know the correct collection name.
Because of correctly executed autorun functions i additionally passed the computation (c) to collectionIsReady().
formObj.collections.forEach(collection => {
Tracker.autorun((c) => {
const handle = Meteor.subscribe(collection);
if (handle.ready()) {
dispatch(collectionIsReady(formObj, collection, c));
if (_.isEqual(formObj.collections, formObj.loadedCollections)) {
dispatch(collectionsAreReady(formObj));
dispatch(formIsReady(formObj));
}
}
});
});

Related

Wait for multiple Promises in Firebase Functions

I try to figure out how I can wait for several promises to be executed before the function ends.
Essentially, this is what I try to do:
if a user deletes his/her account, I want to clean up all data which is associated with him
direct data can be deleted
if a user is part of a group, the group shall still exists, if other users are in that group; otherwise the group shall be deleted as well
Here is what I tried so far:
A) Main function (starts the first level of promises):
export function cleanUpAllData(user) {
const userId = user.uid;
const promises = [];
promises.push(deleteCategoriesData(userId)); // this works fine
promises.push(deleteUserAndGroupData(userId)); // this one has other promises which still run when Promise.all() is finished
Promise.all(promises)
.then(() => {
return "ok"; // this works so far, but not all promises are resolved
})
.catch(errPromises => {
console.log("an error occured during the processing of main promises");
console.log(errPromises, errPromises.code);
return "error";
})
}
B) deleteUserAndGroupData function (the other promise is working fine): each group found in the user data starts another level of promises and also triggers a thrid level of promises (deleteGroup) - the rest is working fine
function deleteUserAndGroupData(userId) {
const promisesUserData = [];
return admin.firestore().collection('users').doc(userId).collection('groups').get()
.then(userGroups => {
userGroups.forEach(userGroupData => {
// delete group data
promisesUserData.push(deleteGroups(userId, userGroupData.id)); // here are other promises that need to be resolved - somewhere is a problem
// delete all Groups in Users (subcollection)
promisesUserData.push(deleteGroupInUser(userId, userGroupData.id)); // this works fine
});
Promise.all(promisesUserData)
.then(() => {
admin.firestore().collection('users').doc(userId).delete()
.then(() => {
return "user data deleted"; // works fine
})
.catch(() => {
console.log("an error occured during deleting of user");
return "error";
});
})
.catch(errPromises => {
console.log("an error occured during the processing of promisesUserData");
console.log(errPromises, errPromises.code);
return "error";
})
})
.catch(errUserGroups => {
console.log(errUserGroups, errUserGroups.code);
return "no groups in User";
});
}
C) deleteGroups functions: deletes the sub collections in the group (works fine) and after that the group shall be deleted if there is no other user (which does not work)
function deleteGroups(userId,groupId) {
const promisesDeleteGroups = [];
// delete groups subcollection data
promisesDeleteGroups.push(deleteGroupRole(userId, groupId));
promisesDeleteGroups.push(deleteGroupUser(userId, groupId));
return Promise.all(promisesDeleteGroups).then(() => {
checkForOthers(groupId);
}).catch(errDeleteGroupSubcollections => {
console.log("an error occured during the processing of promisesDeleteGroups");
console.log(errDeleteGroupSubcollections, errDeleteGroupSubcollections.code);
return "error";
});
}
D) checkForOthers function - checks if there is any entry in the subcollection and shall start to delete the group (but does not)
function checkForOthers(groupId) {
return admin.firestore().collection('groups').doc(groupId).collection('users').get()
.then(groupUsers => {
return "other users exist - group should not be deleted";
})
.catch(errGroupUsers => {
// console.log("no other group members - group can be deleted");
// return "no other group members - group can be deleted";
console.log(errGroupUsers, errGroupUsers.code);
checkForInvitesAndDelete(groupId);
});;
}
E) checkForInvitesAndDelete: first I want to delete another subcollection which might or might not exists, if another user has been invited to the group; then it should trigger the final group delete (which seems not to work)
function checkForInvitesAndDelete(groupId) {
const promisesInvitesAndDelete = [];
return admin.firestore().collection('groups').doc(groupId).collection('invitations').get()
.then(groupInvitations => {
console.log("delete open Group Invitations");
groupInvitations.forEach(groupInvite => {
promisesInvitesAndDelete.push(deleteGroupInvite(groupId, groupInvite.id));
});
Promise.all(promisesInvitesAndDelete)
.then(() => {
deleteGroup(groupId)
})
.catch(errPromisesInvitesAndDelete => {
console.log("an error occured during the processing of deleting group invites");
console.log(errPromisesInvitesAndDelete, errPromisesInvitesAndDelete.code);
return "error";
});
})
.catch(() => {
console.log("no open invitations");
deleteGroup(groupId);
});
}
F) deleteGroup function
function deleteGroup(groupId) {
return admin.firestore().collection('groups').doc(groupId).delete();
}
I am relatively new to programming, especially Firebase Functions, so any help would be appreciated!!
Thank you!
You are not using the return keyword everywhere, where it should be. If you do a async task, you must 'return' it someway.
Some examples:
example A: add return before Promise.all(promises)
... B: add return before Promise.all(promisesUserData)
... C: add return before checkForOthers(groupId)
... D: add return before checkForInvitesAndDelete(groupId)
... E: add return before Promise.all(promisesInvitesAndDelete) and deleteGroup(groupId)
I added the 'return' statements which helped a lot, but that was not the full answer.
In (D) I thought that if my collection has no data, it would run into the 'catch' phase, but it is not. So, I needed to check for an empty result set in my 'then' phase.
if (groupUsers.empty) {
return checkForInvitesAndDelete(groupId);
} else {
return "other users exist - group should not be deleted";
}
Same with the function (E) when there is no open invitation.

Redux-Observable multiple actions in single epic

I'm new to this and there are several similar questions such as redux-observable - dispatch multiple redux actions in a single epic but I can't see how they apply to my usecase.
I'm using a Subject to emit multiple events based on processing a file and uploading to a server
export function UploadSceneWithFile(scene){
const subject$ = new Subject()
FileToScenePreview(scene,scene.file).then(res => subject$.next(res))
const uploader = new S3Upload({
....
onError:()=>subject$.next('error'),
onProgress: (val)=> subject$.next(val),
onFinishS3Put: ()=>subject$.next('complete'),
})
uploader.uploadFile(scene.file)
return subject$
}
A want to write an epic that captures these events and dispatches actions based on the data coming back.
ie. something like this
export function uploadSceneFile(action$) {
return action$.ofType(CREATE_SCENE_SUCCESS)
.mergeMap(({payload}) =>
UploadSceneWithFile(payload)
.subscribe(res => {
console.log(res)
if (!res.thumbName) {
return { type: UPLOAD_SCENE_FAILED, message: 'failed' }
} else {
return {type: UPLOAD_SCENE_SUCCESS, payload: res }
}
if (!res.value) {
return { type: UPLOAD_SCENE_THUMB_FAILED, message: 'failed' }
} else {
return {type: UPLOAD_SCENE_THUMB_SUCCESS, payload: res }
}
})
)
}
I'm getting this error:
TypeError: You provided an invalid object where a stream was expected.
You can provide an Observable, Promise, Array, or Iterable.
i can console log the results OK, but I'm not dispatching any actions.. any ideas how I go about this?
What's happening is that you're returning a subscription inside your mergeMap, instead of an Observable that it expects. Instead of using subscribe it looks like you want to use map
export function uploadSceneFile(action$) {
return action$.ofType(CREATE_SCENE_SUCCESS)
.mergeMap(({payload}) =>
UploadSceneWithFile(payload)
.map(res => { // <------------------ map, not subscribe
console.log(res)
if (!res.thumbName) {
return { type: UPLOAD_SCENE_FAILED, message: 'failed' }
} else {
return {type: UPLOAD_SCENE_SUCCESS, payload: res }
}
if (!res.value) {
return { type: UPLOAD_SCENE_THUMB_FAILED, message: 'failed' }
} else {
return {type: UPLOAD_SCENE_THUMB_SUCCESS, payload: res }
}
})
)
}
In redux-observable you will almost never call subscribe yourself, instead composing Observables. The only time you'd use it is mostly with custom operators that aren't super common.

How to enrich result list from FirebaseListObservable with FirebaseObjectObservables

I have a FirebaseListObservable with IDs and I want to look them up using FirebaseObjectObservable and create a Array of the result, but I don't really understand how to do it.
this.af.database.list('/favourites/'+ this.userId)
.flatMap(favouriteId => {
return this.af.database.object('/dhs/en/'+favouriteId.$value)
})
.subscribe(favourite => {
console.log("favourite", favourite);
});
This doesn't work because favouriteId.$value is undefined. What is the right way to do this?
This is a related question: How to merge an observable that is a property of another observable
EDIT: I have created an ugly temporary solution
getAllFavourites(): Observable<any> {
return Observable.create(observer => {
var resArray = [];
this.af.database.list('/favourites/'+ this.userId)
.subscribe(favouriteArray => {
favouriteArray.forEach((favouriteId) => {
this.secretProvider.getDay(favouriteId.$value)`
.subscribe(favourite => {
resArray.push(favourite);
observer.next(resArray);
});
})
})
})
}
Obviously this is not the purpose of observables and in addition the resulting observable array is not updated correctly when for example a favourite ID is removed from this.af.database.list('/favourites/'+ this.userId).
UPDATE: I have found a better solution following this thread: How to move FirebaseObjectObservable into a FirebaseListObservable
getAllFavourites(): Observable<any> {
return this.af.database.list('/favourites/'+ this.userId)
.map((favourites)=>{
return favourites.map((favourite) => {
favourite = this.secretProvider.getDay(favourite.$value)
return favourite;
})
});
}
(and in the HTML using a nested async varibale as described in the other thread)
I still think there must be a better solution though. Something using mergeMap or similar, and avoiding the nested asyncs.

HTTP: Angular 2+TS How to use Observables in HTTP

I found an example from angular.io. This example is very similar to my app, with same kind of methods. This example is using Promises, but I'm using Observables. If I use this example as a reference, I have every method working in my app, except the getHero method in the service, and the ngOnInit in the HeroDetailComponent. So I'm wondering if someone can help and convert this method to an observable, because I'm having trouble with the syntax. Here is the codes I need converted to Observable and the plunker
//HeroService
getHero(id: number) { // my id is String
return this.getHeroes()
.then(heroes => heroes.filter(hero => hero.id === id)[0]);
}
//HeroDetailComponent
ngOnInit() {
if (this.routeParams.get('id') !== null) {
let id = +this.routeParams.get('id');
this.navigated = true;
this.heroService.getHero(id)
.then(hero => this.hero = hero);
} else {
this.navigated = false;
this.hero = new Hero();
}
}
So I want something like this:
//HeroService
public getHero(id: string) {
return this.getHeroes()
.subscribe(heroes => this.heroes.filter(hero => heroes.id === id)[0]); //BTW, what does this [0] mean??
}
EDIT: I had to actually retrieve the list directly, it didn't work with return this.heroes as suggested in answers below. Working example:
public getById(id: string) {
//return this.getHeroes() <---- didn't work
return this.http.get('someUrl') // WORKS!
.map(heroes => this.heroes.filter(hero => hero.id === id)[0]);
}
Now I'm still having trouble with my ngOnit, and I can't really understand why!
ngOnInit(){
let id = this._routeParams.get('id');
this.heroService.getById(id)
//console.log("retrieved id: ",id ) <----- gives correct id!
.subscribe(hero => this.hero = hero);
//console.log("hero: ", this.hero); <----- gives undefined!
}
EDIT2, still getting undefined when trying to move to the detail page :( I think you had one bracket to much in your answer, tried to look and get the correct places for the brackets?
ngOnInit(){
let id = this._routeParams.get('id');
this.heroService.getById(id)
.subscribe(heroes => {
// this code is executed when the response from the server arrives
this.hero = hero
});
// code here is executed before code from the server arrives
// even though it is written below
}
If you call subscribe() on an Observable a Subscription is returned. You can't call subscribe() on a subscription.
Instead use just an operator (map()) and use subscribe() on the call site:
public getHero(id: string) {
return this.getHeroes()
.map(heroes => this.heroes.filter(hero => heroes.id === id)[0]);
}
ngOnInit(){
let id = this._routeParams.get('id');
this.heroService.getHero(id)
.subscribe(hero => this.hero = hero);
}
In contrary to subscribe(), map() also operates on an Observable but also returns an Observable.
[0] means to just take the first item of the filtered heroes.
update
ngOnInit(){
let id = this._routeParams.get('id');
this._searchService.getById(id)
.subscribe(searchCase => {
// this code is executed when the response from the server arrives
this.searchCase = searchCase;
console.log("id: ", this.searchCase);
});
// code here is executed before code from the server arrives
// event though it is written below
}
This code is a function
searchCase => {
// this code is executed when the response from the server arrives
this.searchCase = searchCase);
console.log("id: ", this.searchCase);
}
that is passed to subscribe() and the Observable calls this function when it has new data for the subscriber. Therefore this code is not executed immediately but only when the observable emits new data.
Code that comes after subscribe() is executed immediately and therefore before above function and therefore this.searchCase does not yet have a value.
This is a way you can do it:
//HeroService
public getHero(id: string) {
return this.getHeroes()
.map(heroes => this.heroes.filter(hero => heroes.id === id)[0]);
}
//HeroDetailComponent
ngOnInit(){
let id = this._routeParams.get('id');
this.heroService.getHero(id)
.subscribe(hero => {
// your code here
});
}
The [0] is an array accessor. You're selecting the first element on array index 0 with it. You need this, because Array.filter() returns a new array with the filtered values, but you only want one hero.

Meteor publication error - Publish function returned an array of non-Cursors

I have this publication
Meteor.publish('temsInThisCompetition', function (id) {
var teams = [];
return Competitions.find(id).fetch().map(function (doc) {
for(var item in doc.teams){
teams.push(Teams.find(item));
}
return teams;
});
});
But I am getting this error
Exception from sub temsInThisCompetition id kDPuEbc9dtWn2tfT3 Error: Publish function returned an array of non-Cursors
This solved the problem for me
Meteor.publish('teamsInThisCompetition', function (id) {
var competition = Competitions.findOne(id);
return Teams.find({_id:{$in:competition.teams}});
});
Since Meteor is asynchronous, your initial code doesn't work because the team array is returned before the for loop is completed.
If you need a for loop in an async environment, you could use a callback function, like this:
function getTeams(id, callback) {
Competitions.find(id).fetch().map(function (doc) {
var teams = [];
for(var item in doc.teams){
teams.push(Teams.find(item));
if(teams.length==doc.teams.length) {
callback(teams);
}
}
});
}
Meteor.publish('temsInThisCompetition', function (id) {
getTeams(id, function(teams) {
return teams;
});
});
Collection.find() without a callback and Collection.findOne() are synchronous, that's why the code in your answer doesn't return an empty set.

Resources