The array gets cleared after mongoose.findOne() - arrays

I am trying to get documents out of the database (questions) and store them in an array for further use (The questions would be asked to the players).
However, when I call findOne(), and add it's result to an array, the array is empty after the callback.. Even though it contains the data in there!
private Questions: iGeneralQuestion[];
private LoadQuestions(): iGeneralQuestion[] {
const result: iGeneralQuestion[] = [];
for (let qid of this.GeneralArguments.questionIds) {
QuestionModel.findOne({ id: qid }, (err: any, question: any) => {
if (err) return err;
if (!question) return question;
this.Questions.push({
questionId: question.id,
question: question.question,
answer: question.answer,
otherOptions: question.otherOptions,
timeLimit: question.timeLimit,
difficulty: question.difficulty
//explanation: question.explanation
//pictureId: question.pictureId
});
console.log(this.Questions); //The array is full here! (Gets filled with each iteration)
});
}
console.log(this.Questions); //But it doesn't contain anything here!
return result;
}
This is the code that loads the documents, and saves their content in the array.
I have tried using the promise functions and find() instead of findOne().. To no avail!
I'm absolutely lost with this, as it shouldn't be about some kind of scope error. The Questions array is a field variable, but it seems to get cleared in the end.
Any help is appreciated!

When you call QuestionModel.findOne({ id: qid }, (err: any, question: any) => {...});, you're registering a callback function that will be invoked after the document is found. But in the meantime (while findOne(...) looks for the document) the rest of the code continues to execute.
So after you've called QuestionModel.findOne(...), the for loop continues. You haven't found a document yet - that's happening in the background. Eventually the for loop will complete and the last console.log(this.Questions) on the page is called followed by return result;. But, findOne(...) is still looking for documents in the background. It hasn't found anything yet, so console.log(this.Questions) doesn't display anything. The array is still empty at this point in time. Awhile later, after findOne(...) finally finds a document, then the callback is called.
It may be worth looking into promises when dealing with asynchronous code like this:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

More optimize way(reduce DB operations) instead of finding each record, find all recode in one shot than creating the result. Hope this helps :)
private LoadQuestions(cb){
const result: iGeneralQuestion[] = [];
QuestionModel.find({
id: {
$in: this.GeneralArguments.questionIds // Array of all ids
}}, (err: any, questions: any) => {
if (err) cb(err);
questions.forEach((question) => {
if (question) {
result.push({
questionId: question.id,
question: question.question,
answer: question.answer,
otherOptions: question.otherOptions,
timeLimit: question.timeLimit,
difficulty: question.difficulty
//explanation: question.explanation
//pictureId: question.pictureId
});
}
});
return cb(null,result);
});
}

Related

Updating multiple queries in useMutation Apollo-client without refetching them

I'm executing this mutation in my NewBook component:
const [addBook] = useMutation(ADD_BOOK, {
update: (cache, response) => {
cache.updateQuery({ query: ALL_BOOKS }, ({ allBooks }) => {
return { allBooks: allBooks.concat(response.data.addBook) };
});
},
refetchQueries: [{ query: ALL_AUTHORS }, { query: ALL_GENRES }],
options: {
awaitRefetchQueries: true,}});
Instead of having to refetch those two queries, I'd like to update them like ALL_BOOKS - but could not find any example in the docs. Does anyone know a way to accomplish that?
Thank you.
What you need to do is make multiple cache updates based on response data.
Once you add your new book to the query, the next step is to fetch all authors.
cache.updateQuery({ query: ALL_BOOKS }, ({ allBooks }) => {
return { allBooks: allBooks.concat(response.data.addBook) };
});
//Get all authors
const existingAuthors = cache.readQuery({
query: ALL_AUTHORS,
//variables: {}
});
//If we never called authors, do nothing, as the next fetch will fetch updated authors. This might be a problem in the future is some cases, depending on how you fetch data. If it is a problem, just rework this to add newAuthor to the array, like allAuthors: [newAuthor]
if(!existingAuthors.?length) {
return null
}
The next thing is that we need to compare the new book's author with existing authors to see if a new author was added.
//continued
const hasAuthor = existingAuthors.find(author => author.id === response.data.createBook.id)
//Double check response.data.createBook.id. I don't know what is returned from response
//If author already exists, do nothing
if(hasAuthor) {
return null
}
//Get a new author. Make sure that this is the same as the one you fetch with ALL_AUTHORS.
const newAuthor = {
...response.data.createBook.author //Check this
}
cache.writeQuery({
query: ALL_AUTHORS,
//variables: {}
data: {
allAuthors: [newAuthor, ...existingAuthors.allAuthors]
},
});
Then continue the same with ALL_GENRES
Note:
If you called ALL_GENERES or ALL_BOOKS with variables, you MUST put the SAME variables in the write query and read query. Otherwise Apollo wont know what to update
Double check if you are comparing numbers or strings for authors and genres
Double check all of the variables I added, they might be named different at your end.
Use console.log to check incoming variables
You can probably make this in less lines. There are multiple ways to update cache
If it doesn't work, console.log cache after the update and see what exactly did apollo do with the update (It could be missing data, or wrong variables.)
Add more checks to ensure some cases like: response.data returned null, authors already fetched but there are none, etc...

Can't access element of Array

I'm adding elements to an array using a service, which successfully adds the elements. I can see the data populated with a console.log
I can't however access the element.
this.routingService.getNode(startNode).subscribe(node => {
node.forEach(node => {
this.openSet.push(new MapNode(node.id, node.lon, node.lat, this.routingService.getNodeNeighbours(node.id)));
});
});
console.log(this.openSet); // this prints out the below screenshot
However, when I use something like:
console.log(this.openSet[0]);
I get output of 'undefined'. I'm not sure if I'm being really thick right now in how I'm accessing it or not...
Any ideas?
subscribe works asynchron, so console.log() will log before the forEach loop runs. It's the same async behaviour as in this little piece of code
let foo = [];
setTimeout(() => {
foo.push(1);
foo.push(2);
}, 500);
console.log(foo); // logs []
Please see the duplicate post for options on how to work with asynchronity.
How to return the response from an asynchronous call?

Async array without using anything async?

It drives me crazy. I have a really simple "problem" and it took me hours and still have no idea whats going on.
I have a child service which inherits from a parent service (I'm using ES6). The constructor takes an 1 argument called options. options will be assigned to this._defaults.
Now before I pass the options into my object (new Service(options)) I populate options with some data. To keep it simple, my current options object looks like this:
const options = {
types: []
}
Now I add some stuff into the types array, like this:
const Types = {
standard: {
some: 'data'
},
freeroll: {
some: 'data'
},
mainevent: {
some: 'data'
},
qualifier: {
some: 'data'
}
};
angular.forEach(Types, (val, key) => {
options.types[key] = true;
});
I assign my service to the scope like this:
$scope.service = new Service(options)
and output the service using console. The console now says the value of _defaults.types is Array(0). When I click on the array the correct values will be shown but the scope is not aware of that.
How is that? Doesn't Array(0) mean that at the time of the console.log() the array wasn't filled with any values but has been later? Like an async function would do?
Here is a plunk of my problem.
The problem is that types is an array and you're treating it like a plain Object. You can solve this one of two ways.
First just change types to an Object:
const options = {
types: {}
};
Or, if you need an Array, change how you're adding items to the array:
angular.forEach(Types, (val, key) => {
options.types.push({
type: key,
value: val
});
});
Note that this is just one way of turning the object into an array, the data structure you end up with is up to you.

Return Array from Firebase snapshot method

I'm connecting to Firebase and retrieving data from a given location, what I'm trying to do is loop through the snapshot.val() build and Array, return the Array and then loop through it on the component.html and build a Dropdown.
At present I'm having difficulty figuring out the syntax for such service method, this is my current code. - This is called from app.component.ts within ngOnInit()
getExerciseOptions(): Array<Exercise> {
const items: Exercise[] = [];
this.exerciseDbRef2.on("value", (snapshot) => {
snapshot.forEach((snap) => {
items.push({
id: snap.key,
description: snap.val().description
});
return false;
});
});
}
So this.exerciseDbRef2 points to the table within Firebase as shown here:
private exerciseDbRef2 = this.fire.database.ref('/exercise_description');
The error I'm currently receiving is
A function whose declared type is neither 'void' nor 'any' must return a value.
Which I understand, so when I change return false to return items the new error is:
Argument of type '(snap: DataSnapshot) => Exercise[]' is not assignable to parameter of type '(a: DataSnapshot) => boolean'.
Type 'Exercise[]' is not assignable to type 'boolean'.
I have looked at using child_added but from my understanding this will be called every time a new child has been added into that location, which is not what I'm looking for. The children in this location will not change nor will any be added. - Maybe I've misinterpreted 'child_added' ?
I'm rather new to Firebase so I'm at the beginning on the learning curve so please bare with, I would also like to mention if the way I'm currently doing this is not correct then please bring it to my attention.
So for clarification : Connect to Firebase, retrieve all children from given location i.e exercise_description table, loop through the snapshot, build an Array and return it.
Then on the component loop through the Array and build the Dropdown.
Can someone please explain how I go about returning the Array based off the snapshot.val() ?
You cannot return an array from getExerciseOptions, as the value event is asynchronous.
However, you could return a promise:
getExerciseOptions(): Promise<Array<Exercise>> {
return this.exerciseDbRef2
.once("value")
.then(snapshot => {
const exercises: Exercise[] = [];
snapshot.forEach(snap => {
exercises.push({
id: snap.key,
description: snap.val().description
});
return false;
});
return exercises;
});
}
You would then call it like this:
getExerciseOptions().then(exercises => console.log(exercises));
If you are unfamiliar with promises, you might want to read JavaScript Promises: an Introduction.

Jaydata saveChanges() counts tracked / changed entities, but doesn't send a batch request (with OData v4 Provider and Web Api)

by working with jaydata i am adding entities to a tree structure with deep nesting of entity-objects.
I attach the upper entity to the context and edit/add/remove related child entities. At the end i use saveChanges() with a promise.
The count-value passed to the promise tells that all changed entities have been counted correctly but saveChanges() didn't execute a batch request, persisting these entities.
So it feels like nothing else happens, but counting entities.
I post a small code example. I am quite sure, that the references of the entites are set correctly. (Working with jaydata, odata v4, web api and angularjs)
Is someone else having this problem with jaydata and found the reason?
Thanks for your help. =)
Greetings Paul
// Attach upper entity
DataService.jaydata.attach(viewModel.currentSkillTree.entity);
// Generating new entities
var newSkill = new DataService.jaydata.Skills.elementType({
Id: undefined,
Name: 'New skill',
Levels: [],
IconId: 47,
SkillTreeUsage: []
});
var newSkillLevel = new DataService.jaydata.SkillLevels.elementType({
Id: undefined,
ShortTitle: 'New level',
Skill: newSkill,
SkillId: undefined,
Level: 1,
RequirementSets: []
});
var newRequirementSet = new DataService.jaydata.RequirementSets.elementType({
Id: undefined,
SkillLevel: newSkillLevel,
SkillLevelId: undefined,
SkillTree: undefined,
SkillTreeId: viewModel.currentSkillTree.entity.Id,
});
var newSkillTreeElement = new DataService.jaydata.SkillTreeElements.elementType({
Id: undefined,
SkillTree: undefined,
SkillTreeId: viewModel.currentSkillTree.entity.Id,
Skill: newSkill,
SkillId: undefined,
Position: { X: x, Y: y }
});
// Completing object-references
viewModel.currentSkillTree.entity.Elements.push(newSkillTreeElement);
newSkill.Levels.push(newSkillLevel);
newSkill.SkillTreeUsage.push(newSkillTreeElement)
newSkillLevel.RequirementSets.push(newRequirementSet);
// Saving
DataService.jaydata.saveChanges()
.then(function (cnt) {
console.log('Saved entities:', cnt);
// The cnt-result in console is 4
// But no request was executed, nothing was saved
}, function (exception) {
console.log(exception); // Also no exception was thrown
});
So to not be that unkind.
The solution to solve the problem above to me, since i tried nearly every combination with entities (adding, attaching, .save(), .saveChanges(), object-references etc, figuring out it doesn't make sense anyway, it just acted the same way and seems to be so buggy), ended up within a workaround acting with classic nested async calls.
The solution was to save entities seperately within nested promises and to turn off the batch behavior of jaydata, to avoid double requests.
You can find the option within $data.defaults
$data.defaults.OData.disableBatch = true;
As result i am dealing now with good old nasty pyramids of doom, which at least gave the possibility back to save entities in the right order, with full control, the way the api needs it.
// Saving new SkillLevelRequirement connection
if (isConnectionGiven === false) {
// The first level of source skill where the target-skill-requirement will be added
var sourceSkillLevel = Enumerable
.From(sourceSkill.Levels)
.FirstOrDefault(null, function (x) {
return x.Level === 1;
});
// The last level of the target-skill to solve
var targetSkillLevel = Enumerable
.From(targetSkill.Levels)
.FirstOrDefault(null, function (x) {
return x.Level === targetSkill.Levels.length;
});
// First set of first level from source skill (will be used to add skilllevel-requirement)
var firstRequirementSet = sourceSkillLevel.RequirementSets[0];
// New RequirementAsignment
var newRequirementAssignment = new DataService.jaydata.RequirementAssignments.elementType({
RequirementSetId: firstRequirementSet.Id,
Order: 1
});
// New Requirement
var newRequirement = new DataService.jaydata.Requirements.elementType({
Title: requirementTypes.SKILL_CONNECTION,
RequirementOfIntId: undefined,
RequirementOfBoolId: undefined,
RequirementOfSkillLevelId: 0
});
// New RequirementOfSkillLevel
var newRequirementOfSkillLevel = new DataService.jaydata.RequirementsOfSkillLevel.elementType({
SkillLevelId: targetSkillLevel.Id,
});
// Loading symbol
showBusyIndicator();
newRequirementOfSkillLevel.save()
.then(function () {
newRequirement.RequirementOfSkillLevelId = newRequirementOfSkillLevel.Id;
newRequirement.save()
.then(function () {
newRequirementAssignment.RequirementId = newRequirement.Id;
newRequirementAssignment.save()
.then(function () {
// Loading symbol will be closed after tree reloaded
reloadCurrentTree();
}, function (exception) {
showJayDataExceptionModal(exception);
});
}, function (exception) {
showJayDataExceptionModal(exception);
});
}, function (exception) {
showJayDataExceptionModal(exception);
});
}
}
#jaydata developers: Thanks for 42 new grey hairs. I'm still at the point where i think i am using your tool wrong and jaydata could do so much better. Better up your documentation, sieriously. No desserts for you today.

Resources