How to access array elements in angular - javascript

From an API, I'm trying to get data using httpModule. Here is my code
async searchMeaning(form: NgForm) {
const post = {
word: form.value.inputWord,
language: form.value.language
}
console.log(post);
if (post.language && post.word) {
this.output1 = await this.callApi(post); // it displays await has not effect
console.log(this.output1) // undefined.
}
}
callApi(post) {
this.http.get('https://api.dictionaryapi.dev/api/v2/entries/'+post.language+'/'+post.word)
.subscribe((data) => {
console.log(JSON.parse(JSON.stringify(data)));
return data;
}, (error : any) => {
return error
})
}
When I use async and await, it says that await has no effect. An undefined is getting assigned to the variable this.output. How can I make this work?
Also, How can I get access to a variable from the below response array?
[
{
"word": "hello",
"phonetics": [
{
"text": "/həˈloʊ/",
"audio": "https://lex-audio.useremarkable.com/mp3/hello_us_1_rr.mp3"
},
{
"text": "/hɛˈloʊ/",
"audio": "https://lex-audio.useremarkable.com/mp3/hello_us_2_rr.mp3"
}
],
"meanings": [
{
"partOfSpeech": "exclamation",
"definitions": [
{
"definition": "Used as a greeting or to begin a phone conversation.",
"example": "hello there, Katie!"
}
]
},
{
"partOfSpeech": "noun",
"definitions": [
{
"definition": "An utterance of “hello”; a greeting.",
"example": "she was getting polite nods and hellos from people",
"synonyms": [
"greeting",
"welcome",
"salutation",
"saluting",
"hailing",
"address",
"hello",
"hallo"
]
}
]
},
{
"partOfSpeech": "intransitive verb",
"definitions": [
{
"definition": "Say or shout “hello”; greet someone.",
"example": "I pressed the phone button and helloed"
}
]
}
]
} ]
here I need to get the value of the definition variable from the above array. How can I do that?
console image

When I use async and await, it says that await has no effect.
Yes, that's because await only has an effect on Promises (a type native to Javascript). this.callApi(post) returns a Subscription (which is a RxJS type), which isn't the same as a Promise.
In Angular, I'd argue that using Promises is an antipattern (unless required by a third party library). Instead you should use Observables and subscribe to them, which you'll find out later has a tons of advantages in more complex situations. The way you usually do this is by building up observables with pipes as far as possible, and then subscribe when you actually need to do the call, like this:
searchMeaning(form: NgForm) {
const post = {
word: form.value.inputWord,
language: form.value.language
}
console.log(post);
if (post.language && post.word) {
this.callApi(post).subscribe(x => {
this.output1 = x;
console.log(this.output1); // shouldn't be undefined anymore
// if you want to do more to
// affect the state of the component,
// you can do it here
});
// Be cautious, things that you write here will actually execute before the call to the API.
}
}
callApi(post) {
this.http.get('https://api.dictionaryapi.dev/api/v2/entries/'+post.language+'/'+post.word)
.pipe(map(x => {
// if you want to change x in some way
// before returning to the calling method,
// you can do it here
return x;
}));
}
Also, How can I get access to a variable from the below response array?
For example, if you want to access the first definition example, you could do the following: x[0]["meanings"][0]["definitions"][0]["example]. You could also make a type definition to make it even easier to access, but it's probably not worth it if you're just using the dictionary for a few things.
It can be worthwhile to look through the Angular docs on Observables, or at least look at the concrete use cases with calling APIs with HttpClient

You should return an observable from callApi() then you can use await on your function:
callApi(post) {
const sub = new Subject();
this.http.get('https://api.dictionaryapi.dev/api/v2/entries/'+post.language+'/'+post.word)
.subscribe((data) => {
console.log(JSON.parse(JSON.stringify(data)));
sub.next(data)
sub.unsubscribe();
}, (error : any) => {
sub.error(data);
sub.unsubscribe();
})
return sub.asObservable();
}
this.output1 = await this.callApi(post).pipe(first()).toPromise();
If you don't want to use .pipe(first()).toPromise(), then you need to have callApi return a Promise.
Also, depending on what you want callApi to do, you could just return this.http.get() or even this.http.get().pipe(first()).toPromise().

Related

How to search an array of objects obtained by axios for an id? Vue 2

I am trying to verify if the user is inside that list that I capture by axios, the issue is that I have used the FILTER option but it always returns undefined or [], being that if the user exists in that array.
I can't think what else to do, because I validate if it is by console.log() the variable with which I ask and if it brings data.
created() {
this.getStagesDefault()
this.getSalesman()
this.getStagesAmountByUser()
},
methods: {
async getSalesman(){
const { data } = await axios.get('salesman')
this.employees = data.data
},
getStagesAmountByUser(){
console.log(this.user['id'])
var objectUser = this.employees.filter(elem => {
return elem.id === this.user['id']
})
console.log(objectUser)
},
Console
Vue data
The method getSalesman is asynchronous, meaning that getStagesAmountByUser will start executing before getSalesman finishes.
Two ways to fix the problem:
Await the getSalesman method, but you have to make the created method async as well. Change the code as follows:
async created() {
this.getStagesDefault()
await this.getSalesman()
this.getStagesAmountByUser()
}
Attach a .then to the getSalesman function, and start the next one inside the .then. Change the code as follows:
created() {
this.getStagesDefault()
this.getSalesman().then(() => this.getStagesAmountByUser())
}
getSalesman is an async method. At the time of the filter, the array being filtered is still empty.
this.getSalesman() // this runs later
this.getStagesAmountByUser() // this runs right away
Have the methods run sequentially by awaiting the async method:
await this.getSalesman()
this.getStagesAmountByUser()
You can avoid the inefficient clientside filtering if you pass the id to the backend and only select by that id.
Additionally, created only gets called once unless you destroy the component which is also inefficient, so watch when user.id changes then call your method again.
Plus don't forget you must wrap any async code in a try/catch else you will get uncaught errors when a user/salesman is not found etc, you can replace console.error then with something which tells the user the error.
{
data: () => ({
employee: {}
}),
watch: {
'user.id' (v) {
if (v) this.getEmployee()
}
},
created() {
this.getEmployee()
},
methods: {
getEmployee() {
if (typeof this.user.id === 'undefined') return
try {
const {
data
} = await axios.get(`salesman/${this.user.id}`)
this.employee = data.data
} catch (e) {
console.error(e)
}
}
}
}

Typescript/JS recursive promises

I'm learning Javascript and I am stuck using Promises.
I'm trying to make a tree like structure from API documentation, where a $ref key in the JSON is replaced with the API object that resides somewhere else in the file. This needs to happen fairly synchronous where I go through the keys of the API object, when I find a $ref it's looked up and replaced in the JSON.
e.g.
"apiStorageVersion": {
"description": "...",
"properties": {
"apiVersion": {
"description": "...",
"type": "string"
},
"kind": {
"description": "...",
"type": "string"
},
"metadata": {
"$ref": "#/definitions/apiMetaData",
"description": "..."
},
"spec": {
"$ref": "#/definitions/apiSpec",
"description": "..."
},
"status": {
"$ref": "#/definitions/apiStatus",
"description": "..."
}
}
}
I start of with this function that gets a list of API objects that I consider parents in a sense that these are more important objects in the API. Definitions is the content of the file and holds all the API objects.
function fillObjectTree(parents: string[]) {
console.log(parents);
Object.keys(definitions).map(apiTitle => {
// if the apiTitle is part of the parents list than we process it
if (parents.includes(apiTitle)) {
// Read the children and see if any references are part of the parent
let par = readChildren(definitions[apiTitle])
par.then(function (vals) {
// Do something with values
})
}
})
}
Next step is reading the properties of this API object and looking for $ref keys.
function readChildren(definition: { properties: any; }) {
return new Promise((resolve, reject) => {
// Get the properties of the definition
let props = definition.properties;
// Properties are not always present on an object.
if (props) {
Object.keys(props).map(propName => {
Object.keys(props[propName]).map(elem => {
if (elem.includes("$ref")) {
// locationURL = the full url for the reference
let locationURL: string = props[propName][elem];
// returns the needed value for the URL based on the regex
let partialURL: string = locationURL.match('(?<=(\#\/.*\/)).*')[0];
readReference(partialURL).then((body) => {
console.log(body);
delete definition.properties[propName];
definition.properties[propName] = body;
});
}
})
})
resolve(definition);
} else {
resolve(definition);
}
});
}
When a reference is found a second function is called that looks in the current file for this object.
function readReference(apiTitle: string) {
return new Promise((resolve, reject) => {
// Check all the definitions and find a match
Object.keys(definitions).map(apiDef => {
if (apiTitle === apiDef) {
readChildren(definitions[apiDef]).then((body) => {
resolve(body);
})
}
})
})
}
So what's going wrong?
Well the order of operations does not seem to match what I want to happen. The object is not replaced in the JSON and not waited for when executing. I rather not used await or async but keep it to baseline Promises if possible.
readChildren will execute synchronously until it gets to this block:
readReference(partialURL).then((body) => {
console.log(body);
delete definition.properties[propName];
definition.properties[propName] = body;
});
readReference will return a promise, so it gets the promise then schedules whatever is inside the then to happen at some time in then future. Then the function continues, eventually calling resolve(definition); and then falling out of the function. This happens BEFORE whatever is inside the then.
To make resolve(definition); happen after everything else, simply put it in the then block too.
Edit: the above solution doesn't handle the map.
Handling lists of asynchronous results:
const promises = list.map(element => {
return someAsyncFunction(element);
});
Promise.all(promises)
.then(results => {
... do stuff with the results
});
Btw, all of this get flattened out if you use the vastly superior async/await syntax. It becomes much easier to reason about the ordering.

Conditionally execute functions that have callbacks each returning values when triggered

I have an array that looks like this :
"attributes": [{"id": "5dad5242e7038210842ec59c","value": 3},{"id": "5dbade6a824f3e2244b2870b","value": 6},{"id": "5d7c943a5759f91e187520cc","value": 17}]
Each value in the array corresponds to a schema from where I will fetch that data.
Ex : if(value == 1){ fetch data from schemaA}
Based on the data fetched from each schema I will repopulate the array with additional information, so at the end the array would look like this:
"attributes": [{"id": "5dad5242e7038210842ec59c","value": 3, "name": "attributeA"},{"id": "5dbade6a824f3e2244b2870b","value": 6, "name": "attributeF"},{"id": "5d7c943a5759f91e187520cc","value": 17, "name": "attributeQ"}]
So far I have written a function :
exports.fetchAttributes = (attr, callback) => {
try {
switch (attr.value) {
case 3:
this.fetchAttributeC(attr.id, (err, attributeB) => {
callback(err, attributeB);
});
break;
case 6:
this.fetchAttributeF(attr.id, (err, attributeF) => {
callback(err, attributeF);
});
break;
case 7:
this.fetchAttributeQ(attr.id, (err, attributeQ) => {
callback(err, attributeQ);
});
break;
default : console.log("Invalid value");
}
} catch(e){
console.log("Catch in fetchAttributes "+e);
return callback(e, null);
}
}
The above function is being called within another function:
exports.functionAttributes = (attributes, callback) => {
let newAttributes = [];
attributes.forEach((attr) => {
this.fetchAttributes(attr, (err, attributeFull) => {
newAttributes.push(attributeFull);
})
}
//need to pass in callback here, but obviously there is a scope issue
}
I need this array with newAttributes as I have to pass it to the final callback in async waterfall where this function is. Due to the scope issue newAttributes is always empty. I need help finding a better way to achieve this. So I can finally end up with result array as I have shown above. Any help is greatly appreciated.
P.S : I have tried conditional callbacks, promises but I couldn't make it work as I need to pass parameters in each step. Feel free to point out where I am wrong, or if there is a better method of achieving this, I would be happy to learn.
I think a promise list would perfectly solve your problem as long as you structured it correctly.
First: Make fetchAttributes return a promise:
return new Promise(function(resolve, reject) {
...Your try catch using resolve() and reject()
})
Second: Create the Array of your promises
let fetchList= [];
attributes.forEach((attr) => {
fetchList.push(...your function)
}
Third: If you don't care whether one fails, make sure to add in a reflection so the promise.all() always completes -> Check this stack post
Finally Once your promise.all() resolves, you can loop through all of the returned values and push them to the newAttributes variable!!!
If you have trouble with it I can provide more info but that should get you started!

Returning dates / server timestamps in cloud callable functions

I'm using a callable function from a client app to retrieve some data from firestore. The data is created using:
projectData = {
created: firebase.firestore.FieldValue.serverTimestamp(),
...otherData
}
firebase.firestore().collection(projectsCollection).add(projectData)
And I can see the timestamp is correctly saved in the firestore console. The callable function does some other things and has error handling, but the data retrieval is done like this (using lodash to expand each document into an object to return to the client):
const projectRef = firestore.collection(gProjectsCollection).orderBy('created')
return projectRef.get().then(snapshot => {
return {
projects: _.chain(snapshot.docs)
.keyBy('id')
.mapValues(s => s.data())
.value()
}
})
This mostly works, but the returned object has a mangled created property (shown here in functions shell output):
RESPONSE RECEIVED FROM FUNCTION: 200, {
"result": {
"projects": {
"XcRyQyaxLguRdbNmxQdn": {
"name": "c",
"description": "c",
"created": {
"_seconds": 1543405587,
"_nanoseconds": 169000000
}
}
}
}
}
I'm presuming this is because the callable function uses JSON.stringify() internally, and the server timestamp isn't converted correctly. I tried explicitly converting the timestamp to a date like this:
return projectRef.get().then(snapshot => {
return {
exVersion,
exId,
projects: _.chain(snapshot.docs)
.keyBy('id')
.mapValues(s => s.data())
.forEach(d => { d.created = d.created.toDate() })
.value()
}
})
but now I get an empty object back:
RESPONSE RECEIVED FROM FUNCTION: 200, {
"result": {
"projects": {
"XcRyQyaxLguRdbNmxQdn": {
"name": "c",
"description": "c",
"created": {}
}
}
}
}
I suspect the real problem here is that callable functions aren't set up to return date objects. If I go one more step and convert the timestamp into a string, I finally get something back in the client:
return projectRef.get().then(snapshot => {
return {
exVersion,
exId,
projects: _.chain(snapshot.docs)
.keyBy('id')
.mapValues(s => s.data())
.forEach(d => { d.created = d.created.toDate().toISOString() })
.value()
}
})
gives:
RESPONSE RECEIVED FROM FUNCTION: 200, {
"result": {
"exVersion": 9,
"exId": null,
"projects": {
"XcRyQyaxLguRdbNmxQdn": {
"name": "c",
"description": "c",
"created": "2018-11-28T11:46:27.169Z"
}
}
}
}
I couldn't find anything in the documentation for callable functions about special handling for dates, but am I missing something in how this should be done?
Some further analysis in the callable function suggests JSON.stringify() is involved, but isn't the whole problem:
console.log(JSON.stringify(d.created)):
info: {"_seconds":1543405587,"_nanoseconds":169000000}
JSON.stringify(d.created.toDate())
into: "2018-11-28T11:46:27.169Z"
So calling d.created.toDate() should be sufficient. But I've had other cases where Date objects are just not returned by callable functions, e.g.:
const testObject = {
some: 'thing',
d: new Date()
}
console.log('JSON:', JSON.stringify(testObject))
return projectRef.get().then(snapshot => {
return {
testObject,
projects: _.chain(snapshot.docs)
.keyBy('id')
.mapValues(s => s.data())
.forEach(d => { console.log('d.created is:', JSON.stringify(d.created.toDate())); d.created = d.created.toDate() })
.value()
}
})
Note in the output below, the logged results of JSON.stringify() on date objects seems to work, but when those objects are contained within the object returned from the function, they both come out as empty objects:
firebase > getAllProjects({uid: 1234})
Sent request to function.
firebase > info: User function triggered, starting execution
info: JSON: {"some":"thing","d":"2018-11-28T13:22:36.841Z"}
info: Got 1 projects
info: d.created is: "2018-11-28T11:46:27.169Z"
info: Execution took 927 ms, user function completed successfully
RESPONSE RECEIVED FROM FUNCTION: 200, {
"result": {
"testObject": {
"some": "thing",
"d": {}
},
"projects": {
"XcRyQyaxLguRdbNmxQdn": {
"description": "c",
"created": {},
"name": "c"
}
}
}
}
From the documentation:
To send data back to the client, return data that can be JSON encoded.
Since Date is not a JSON type, you will have to use your own serialization format for it (at least if you want it to be reliable/portable). From the linked answer it sounds like Date.toJSON is a great candidate for that.

Iterating over Promises recursively

I have an REST API interface which only gets me the first level of some information.
So for example I want to collect groups. Every Group can have subgroups. So for example "Group 1" has the Subgroups "Group A" and "Group B". "Group A" has the Subgroup "GroupX". And so on.
But the API only gives me the first level of Groups for a group name. So I pass "Group 1" to the API and it returns "Group A" and "Group B". To get the supgroups of Group A, I need to call the API again. But i don't know how many iterations of this it will have.
So I thought about using recursion but I haven't come far.
So far my Code:
getGroupChildren(group:string){ return this restService.getGroupChildren(group)}
getGroups():Promise<any>{
let collection:string[] = [];
return this.getGroupChildren("Group A").then((result)=> {
if(result.data.length !==0){
return this.getGroupChildren(data[0].groupName);
}
});
}
Now this will only return me the first Supgroups of the first element.
How can I accomplish it will always find every Supgroup no matter how many? Maybe is it good to use Observables?
Here an example structure of one API call:
{ "groupName" : "Group_1", "children" : ["Group_A", "Group_B"]}
You can achieve what you want with flatMap operator of Observable
getGroups(group: string) {
return this.http.get(`/group/{group}`).flatMap(response => {
if (response.children.length === 0) { // you hit a leaf, stop recursion here
return Observable.of(response);
} else { // there are more levels to go deeper
return this.getGroups(response.children[0].groupName);
}
});
}
Edit Using Promise
Let's say you use a GroupService which returns the data instead of HttpClient. You can convert a Promise to an Observable with fromPromise operator.
getGroups(group: string) {
return Observable.fromPromise(this.groupService.get(group)).flatMap(response => {
if (response.children.length === 0) { // you hit a leaf, stop recursion here
return Observable.of(response);
} else { // there are more levels to go deeper
return this.getGroups(response.children[0].groupName);
}
});
}
Edit 2 Using this service
Let's take a look at your example. You have following json
{
"groupName": "Group_1",
"children" : ["Group_A", "Group_B"]
}
In your component file, you call the service as follows
...
this.recursiveGroupService.getGroups("Group_1")
.subscribe(response => {
// at this point response will be `Group_A`
})
Edit 3 Getting the whole object
This time we'll use forkJoin and call getGroups for all of the children and collect the results in a children array.
Note: I haven't tested this code myself. It may contains some error. If it has, let me know.
import { forkJoin, of } from 'rxjs';
import { map } from 'rxjs/operators';
getGroups(group: string) {
let retVal;
return Observable.fromPromise(this.groupService.get(group)).flatMap(response => {
retVal = {
groupName: response.groupName
};
if (response.children.length === 0) { // you hit a leaf, stop recursion here
return of(retVal);
} else { // there are more levels to go deeper
// this will create list of observable for each child
const children$ = response.children.map(
child => this.getGroups(child));
// forkJoin will execute these observables in parallel
return forkJoin(children$).pipe(
map(results => {
// results is an array containing children data
retVal.children = results;
return retVal;
})
);
}
});
}
You could use Promise.all to recursively resolve deeper children, and then take the result (an array) to create an object to resolve the promise with:
getGroups(groupName = "Group A") {
return this.getGroupChildren(groupName).then((result) =>
Promise.all(result.data.map( ({groupName}) => this.getGroups(groupName) ))
).then(children => ({ groupName, children }));
}
So the promised value could be something like:
[{
groupName: "Group A",
children: [{
groupName: "Group A1",
children: []
}, {
groupName: "Group A2",
children: []
}]
}]

Categories