Nested Observables from nested http requests in Angular - ajax

Here's what I intend to do.
Request https://reqres.in/api/users/2
Which sends a response as follows.
{
"data": {
"id": 2,
"first_name": "Janet",
"last_name": "Weaver",
"avatar":
"https://s3.amazonaws.com/uifaces/faces/twitter/josephstein/128.jpg"
}
}
Now, I wanna grab the avatar url and make another request, which gives me the image binary.
At the end of this, what I want as output is an Observable that gives me this data.
{
"data": {
"id": 2,
"first_name": "Janet",
"last_name": "Weaver",
"avatar":
"BINARY_GIBBERISH HERE"
}
}
Here's how I approached but can't finish it.
this.http
.get('https://reqres.in/api/users/2')
.switchMap(a => {
const image$ = this.http.get(a.json().data.avatar);
const data = Observable.of(a.json().data);
// Do something here to combine.
})
.subscribe(a => {
// get data here.
});
}
Basically, is there any way to have a structure like this,
{
"data": {
"id": 2,
"first_name": "Janet",
"last_name": "Weaver",
"avatar": [Observable from this.http.get]
}
}
Which then gets resolved to my final data?

You don't need to use switchMap for this and use concatMap or mergeMap instead:
this.http
.get('https://reqres.in/api/users/2')
.concatMap(a => {
const data = a.json().data;
const image$ = this.http.get(data.avatar);
return image$.map(imageData => {
data.avatar = imageData;
return data;
});
})
.subscribe(a => {
// get data here.
});
}

Related

is there a way to choose which object to return based on it's attribute in vuex?

I have an array of objects named Employees and am trying to get the ones who are department_id=3 and I don't want to go back to laravel and make another request so is there a way to do so with vuex?
"id": 25,
"name": "name",
"email": "name#gmail.com",
"last_name": "lastname",
"phone": "98745632",
"gender": "Male",
"nationality": "Tunisian",
"school": "ISIMM",
"experience_level": "2",
"source": "Linkedin",
"hiring_date": "2020-04-17",
"end_contract": "2020-04-18",
"position": "web developer",
"grade": "Junior",
"contract": "Cdi",
"department_id": 1,
"company_id": 1,
"archived": "0",
"img": null,
"supervisor_id": 1,
"ipAdress": null,
"last_login_at": null,
"department": {
"id": 1,
"name": "mobile"
},
here's
state :
const employee = {
state: {
Employees: [],
sysAdmins: [],
},
here's
getters :
sysAdmins: (state) =>
state.Employees.map((element) => (element.department_id = "3")),
Employees: (state) => state.Employees,
here's
mutations :
getsysAdmins(state, employees) {
state.sysAdmins = employees;
},
getEmployees(state, employees) {
state.Employees = employees;
},
here's
actions :
getEmployees(context) {
const config = {
headers: {
"x-api-key": process.env.VUE_APP_SIRH_X_API_KEY,
Authorization: localStorage.getItem("access_token"),
},
};
return new Promise((resolve, reject) => {
axios
.get("/employees/all_employees", config)
.then((response) => {
context.commit("getEmployees", response.data.data.users);
context.commit("getsysAdmins", response.data.data.users);
resolve(response);
})
.catch((error) => {
reject(error);
});
});
},
If I understand it right, you want to return the while Employee Object for those employees that work in a certain department.
You can do this by filtering your Employees array. I would write that as following:
getters: {
employees: (state) => state.Employees,
sysAdmins: (state) => state.Employees.filter((employee) => employee.department_id === 3),
// If your DB returns a string as the department_id, you'll have to check against "3"
}
If sysAdmins is always a subset of the employees, it makes more sense to always use a getter instead of placing these in a separate array in the state.
Some other notes:
- You mutations are called get... while these are setters, it might be best to rename these.
- In your action, you currently set the same result as employees and sysAdmins. Again, I would just set employees and always filter the sysAdmins from that array.
try this..
check the result using console.log to make it sure.
const newValue = {
...JSON.parse(localStorage.getItem("Employees")),
department.id: 3
}
localStorage.setItem("Employees", JSON.stringify(newValue))
or
// Get the user from localStorage and parse into object
let department = JSON.parse(localStorage.getItem("Employees"));
// Update an attribute on the user object
Employees.department.id = 2;
// Update the user in localStorage and stringify to string
localStorage.setItem("Employees", JSON.stringify(Employees));

Is there any cost advantage of Parse.Object.saveAll vs. saving individually?

The Parse JS SDK provides a Parse.Object.saveAll() method to save many objects with one command.
From looking at ParseServerRESTController.js it seems that each object is saved individually:
if (path === '/batch') {
let initialPromise = Promise.resolve();
if (data.transaction === true) {
initialPromise = config.database.createTransactionalSession();
}
return initialPromise.then(() => {
const promises = data.requests.map(request => {
return handleRequest(
request.method,
request.path,
request.body,
options,
config
).then(
response => {
return {
success: response
};
},
error => {
return {
error: {
code: error.code,
error: error.message
},
};
}
);
});
return Promise.all(promises).then(result => {
if (data.transaction === true) {
if (
result.find(resultItem => typeof resultItem.error === 'object')
) {
return config.database.abortTransactionalSession().then(() => {
return Promise.reject(result);
});
} else {
return config.database.commitTransactionalSession().then(() => {
return result;
});
}
} else {
return result;
}
});
});
}
It seems that saveAll is merely a convenience wrapper around saving each object individually, so it still does seem to make n database requests for n objects.
It it correct that saveAll has no cost advantage (performance, network traffic, etc) vs. saving each object individually in Cloud Code?
I can tell you that the answer is that Parse.Object.saveAll and Parse.Object.destroyAll batch requests by default in batches of 20 objects. But why take my word for it? Let's test it out!
Turn verbose logging on and then run the following:
const run = async function run() {
const objects = [...Array(10).keys()].map(i => new Parse.Object('Test').set({i}));
await Parse.Object.saveAll(objects);
const promises = objects.map(o => o.increment('i').save());
return Promise.all(promises);
};
run()
.then(console.log)
.catch(console.error);
And here's the output from the parse-server logs (I've truncated it, but it should be enough to be apparent what is going on):
verbose: REQUEST for [POST] /parse/batch: { // <--- note the path
"requests": [ // <--- an array of requests!!!
{
"method": "POST",
"body": {
"i": 0
},
"path": "/parse/classes/Test"
},
... skip the next 7, you get the idea
{
"method": "POST",
"body": {
"i": 9
},
"path": "/parse/classes/Test"
}
]
}
.... // <-- remove some irrelevent output for brevity.
verbose: RESPONSE from [POST] /parse/batch: {
"response": [
{
"success": {
"objectId": "szVkuqURVq",
"createdAt": "2020-03-05T21:25:44.487Z"
}
},
...
{
"success": {
"objectId": "D18WB4Nsra",
"createdAt": "2020-03-05T21:25:44.491Z"
}
}
]
}
...
// now we iterate through and there's a request per object.
verbose: REQUEST for [PUT] /parse/classes/Test/szVkuqURVq: {
"i": {
"__op": "Increment",
"amount": 1
}
}
...
verbose: REQUEST for [PUT] /parse/classes/Test/HtIqDIsrX3: {
"i": {
"__op": "Increment",
"amount": 1
}
}
// and the responses...
verbose: RESPONSE from [PUT] /parse/classes/Test/szVkuqURVq: {
"response": {
"i": 1,
"updatedAt": "2020-03-05T21:25:44.714Z"
}
}
...
In the core manager code, you do correctly identify that we are making a request for each object to the data store (i.e. MongoDB), This is necessary because an object may have relations or pointers that have to be handled and that may require additional calls to the data store.
BUT! calls between the parse server and the data store are usually over very fast networks using a binary format, whereas calls between the client and the parse server are JSON and go over longer distances with ordinarily much slower connections.
There is one other potential advantage that you can see in the core manager code which is that the batch is done in a transaction.

loopback REST API filter by nested data

I would like to filter from REST API by nested data. For example this object:
[
{
"name": "Handmade Soft Fish",
"tags": "Rubber, Rubber, Salad",
"categories": [
{
"name": "women",
"id": 2,
"parent_id": 0,
"permalink": "/women"
},
{
"name": "kids",
"id": 3,
"parent_id": 0,
"permalink": "/kids"
}
]
},
{
"name": "Tasty Rubber Soap",
"tags": "Granite, Granite, Chair",
"categories": [
{
"name": "kids",
"id": 3,
"parent_id": 0,
"permalink": "/kids"
}
]
}
]
is comming by GET /api/products?filter[include]=categories
and i would like to get only products which has category name "women". How do this?
LoopBack does not support filters based on related models.
This is a limitation that we have never had bandwidth to solve, unfortunately :(
For more details, see the discussion and linked issues here:
Filter on level 2 properties: https://github.com/strongloop/loopback/issues/517
Filter by properties of related models (use SQL JOIN in queries): https://github.com/strongloop/loopback/issues/683
Maybe you want to get this data by the Category REST API. For example:
GET /api/categories?filter[include]=products&filter[where][name]=woman
The result will be a category object with all products related. To this, will be necessary declare this relation on the models.
Try like this.It has worked for me.
const filter = {
where: {
'categories.name': {
inq: ['women']**strong text**
}
}
};
Pass this filter to request as path parameters and the request would be like bellow
GET /api/categoriesfilter=%7B%22where%22:%7B%categories.name%22:%7B%22inq%22:%5B%women%22%5D%7D%7D%7D
Can you share how it looks like without filter[include]=categorie, please ?
[edit]
after a few questions in comment, I'd build a remote method : in common/models/myModel.js (inside the function) :
function getItems(filter, categorieIds = []) {
return new Promise((resolve, reject) => {
let newInclude;
if (filter.hasOwnProperty(include)){
if (Array.isArray(filter.include)) {
newInclude = [].concat(filter.include, "categories")
}else{
if (filter.include.length > 0) {
newInclude = [].concat(filter.include, "categories");
}else{
newInclude = "categories";
}
}
}else{
newInclude = "categories";
}
myModel.find(Object.assign({}, filter, {include: newInclude}))
.then(data => {
if (data.length <= 0) return resolve(data);
if (categoriesIds.length <= 0) return resolve(data);
// there goes your specific filter on categories
const tmp = data.filter(
item => item.categories.findIndex(
categorie => categorieIds.indexOf(categorie.id) > -1
) > -1
);
return resolve(tmp);
})
}
}
myModel.remoteMethod('getItems', {
accepts: [{
arg: "filter",
type: "object",
required: true
}, {
arg: "categorieIds",
type: "array",
required: true
}],
returns: {arg: 'getItems', type: 'array'}
});
I hope it answers your question...

How to use the post_poll method in Zapier CLI

According to the docs, I should use a post_poll function to add the missing id field in the response.
How do I add the post_poll function ?
Here's my error:
Results must be an array, got: object,
({"totalevents":83,"events":[{"eventid":10266033,"c)
- Got a result missing the "id" property (83)
Tried following this but it is not clear to me, I'm very new to Zapier-CLI
Update - adding code
This is the function that returns the data:
const listEvents = (z, bundle) => {
console.log('listing events.. ');
let client_id = bundle.inputData.client_id;
const requestOpts = {
url: `https://wccqa.on24.com/wcc/api/v2/client/${client_id}/event`
};
return z.request(requestOpts)
.then((response) => {
return z.JSON.parse(response.content);
});
};
The sample response is the following, with the distiction that I added the id param manually to avoid errors when zapier test|push:
{
"id": 9964513,
"eventid": 9964513,
"archivestart": "2017-09-21T10:30:00-07:00",
"archiveend": "2018-09-21T10:30:00-07:00",
"description": "Zapier Event Test",
"iseliteexpired": "N",
"displaytimezonecd": "America/Bogota",
"eventtype": "Live Webcam ",
"regrequired": true,
"clientid": 22921,
"liveend": "2017-09-21T10:00:00-07:00",
"createtimestamp": "2017-09-21T09:47:44-07:00",
"audienceurl": "https://localhost.on24.com/wcc/r/9964513/C49755A02229BD48E6010848D7C81EF8",
"lastmodified": "2017-09-21T09:47:44-07:00",
"livestart": "2017-09-21T08:45:00-07:00",
"goodafter": "2017-09-21T09:00:00-07:00",
"regnotificationrequired": true,
"isactive": true,
"localelanguagecd": "en"
}
The ACTUAL response from the endpoint the following which is used in the app created in the Web Builder App instead of CLI and works fine:
{
"events": [
{
"eventid": 9964513,
"archivestart": "2017-09-21T10:30:00-07:00",
"archiveend": "2018-09-21T10:30:00-07:00",
"description": "Zapier Event Test",
"iseliteexpired": "N",
"displaytimezonecd": "America/Bogota",
"eventtype": "Live Webcam ",
"regrequired": true,
"clientid": 22921,
"liveend": "2017-09-21T10:00:00-07:00",
"createtimestamp": "2017-09-21T09:47:44-07:00",
"audienceurl": "https://localhost.on24.com/wcc/r/9964513/C49755A02229BD48E6010848D7C81EF8",
"lastmodified": "2017-09-21T09:47:44-07:00",
"livestart": "2017-09-21T08:45:00-07:00",
"goodafter": "2017-09-21T09:00:00-07:00",
"regnotificationrequired": true,
"isactive": true,
"localelanguagecd": "en"
}
],
"totalevents": 1
}
I was thinking something along the line of the following, but how do I register this ?
const postPoll = (event,z,bundle) => {
if(event.key === 'events'){
var results = z.JSON.parse(bundle.request.data).results;
var events = results.events.map(function(event){
event.id = event.eventid;
return event;
});
bundle.request.data = events;
}
};
module.exports = postPoll;
Nice, so you're almost there! CLI apps don't have pre_ and post_ poll methods. Instead, you put any manipulation after the response comes in.
const listEvents = (z, bundle) => {
console.log('listing events.. ');
let client_id = bundle.inputData.client_id;
const requestOpts = {
url: `https://wccqa.on24.com/wcc/api/v2/client/${client_id}/event`
};
return z.request(requestOpts)
.then((response) => {
return z.JSON.parse(response.content);
})
.then(data => {
const events = data.events; // array of events
return events.map(function(e){ // returns array of objects with `id` defined
e.id = e.event_id
return e
})
})
};

How to create *static* property in can.Model

I need somehow to store metadata in the can.Model
I use findAll method and receive such JSON:
{
"metadata": {
"color": "red"
},
"data": [
{ "id": 1, "description": "Do the dishes." },
{ "id": 2, "description": "Mow the lawn." },
{ "id": 3, "description": "Finish the laundry." }
]
}
I can work with data like can.Model.List, but I need metadata like a static property or something.
You can use can.Model.parseModels to adjust your response JSON before it's turned into a can.Model.List.
parseModels: function(response, xhr) {
var data = response.data;
var metadata = response.metadata;
var properties;
if(data && data.length && metadata) {
properties = Object.getOwnPropertyNames(metadata);
can.each(data, function(datum) {
can.each(properties, function(property) {
datum[property] = metadata[property];
});
});
}
return response;
}
Here's a functional example in JS Bin: http://jsbin.com/qoxuju/1/edit?js,console

Resources