I have an observable that gets data from an API, this is hooked up to a paginating feature. How do I add the response data object on to the variable which holds the first pages data. Essentially what Array push does.
Right now each pagination will send a request to the API, even the previous pages since I am overwriting the data. I would like to add the array of objects (data.articles) on to the end of the category that is selected.
data = {
general: undefined,
business: undefined,
entertainment: undefined,
health: undefined,
science: undefined,
sports: undefined,
technology: undefined
};
this.newsService.getNews(endpoint, params).subscribe(data => {
this.data[category] = data.articles;
});
I expect the data to work like this:
If i show 1 article on the general tab, data.general will hold an array with one object in it. When i click next page, data.general should hold an array with 2 objects in it, and so on.
Solution
Array.prototype.push worked, for some reason when i initially tried this it didn't work. But i think that was because it was undefined initially. The below however works fine.
if (!this.data[category]) {
this.data[category] = data.articles;
} else {
Array.prototype.push.apply(this.data[category], data.articles);
}
Try this, maybe this is what you want.
Array.prototype.push.apply(this.data[category], data.articles);
Above code will merge to array of objects into one and set to this.data[category]
I know you have already accepted an answer, but if you were interested in retaining the data in streams instead of in arrays, you can do something like this:
// Action Stream
private productInsertedSubject = new Subject<Product>();
productInsertedAction$ = this.productInsertedSubject.asObservable();
// Merge the streams
productsWithAdd$ = merge(
this.products$, // Original stream of data
this.productInsertedAction$ // New data
)
.pipe(
scan((acc: Product[], value: Product) => [...acc, value]),
catchError(err => {
console.error(err);
return throwError(err);
})
);
And if you were adding an array instead of a single value, the technique would be similar.
Related
I am making a chat app and I would like to save messages in the correct order.
Imagine, I would have a static number of messages
// 4 messages. array of static length: 4
chatMessages: string[] = ['hello', 'world', 'and', 'stack overflow members']; //
now, let's create a save observables for them.
chatMessages: Observable<ChatMessage>[] = chatMessages.map((message: string) => {
return chatService.saveMessage(message); // returns an Observable to call API
})
Now, I want to save them one by one, one after another.
I do it this way:
from(chatMessages).pipe(
concatMap((observable) => observable),
toArray(),
take(1)
).subscribe();
Now My question is, if the initial chatMessages array is dynamic - (can be added a message in any point of time, even during saving).
How do I loop the array to save chat messages one by one, keeping the order they were added ?
For example: two out of four messages were saved, the 3rd is being processed, and in that moment the 5th message is added to the chatMessages array. How do I manage that?
If the initial chatMessages array is dynamic - (can be added a message in any point of time, even during saving)
You are describing an Observable of Message (string), not an array! Since you are processing items one at a time, there is no need for array.
You can just use a simple subject that emits messages as they are received and have one subscription to that stream that saves the messages for you:
chatMessage$ = new Subject<string>();
function saveMessage(message: string) {
chatMessage$.next(message);
}
chatMessage$.pipe(
concatMap(message => chatService.saveMessage(message))
).subscribe();
This will processes the new messages one at a time, in the correct order.
If I understand right the problem, you have to deal with with an array which can receive additional element over time and such elements have to be added at the end of the array.
This can be seen as a stream of messages, the ones originally stored in the array being the first elements of the stream, to which it is possible to add other messages over time, for instance calling a specific function addMessage(msg).
The code to build such stream could look like this
const myInitialArray = ['hello', 'world', 'and', 'stack overflow members']
function saveMessage(msg: string) {
return of(`saved: ${msg}`).pipe(delay(1000))
}
const add$ = new Subject<string>()
const myStream = merge(from(myInitialArray), add$).pipe(
concatMap(msg => saveMessage(msg))
)
Now what you have to do is to subscribe to myStream and, any time you want to add a message at the end of the array (or stream), you have to call the function addMessage.
This stackblitz shows the example working.
Context
I have a front end app that requires an array of blog posts from the API, and when you call http://strapi-url/posts/ with a GET request, it returns all the results as objects in an array. Happy days.
Problem
Eventually I want to have more complex GET options with query params, so I need to modify the post controller and write a custom function for find().
When I modify the find() function in api/post/controllers/post.js , and just make it return the result of strapi.query('post').find(), it returns an object with keys rather than an array.
Code
async find(ctx) {
let entity = await.strapi.query('post').find();
return sanitizeEntity(entity, { model: strapi.models.post });
},
I know I could simply convert it into an array in the front end, but feels like a messy solution, and I would rather understand why it doesn't return an array, and what would be the best way to approach a solution.
The code in sanitizeEntity actually does this. You can check it out in the source code(node_modules/strapi-utils/lib/sanitize-entity.js). Also you can see this by dropping the sanitizeEntity row - you will get an array from await.strapi.query('post').find().
You can run the following test (add a custom endpoint) to see the results:
async test2(ctx) {
let entity = await strapi.query('post').find();
ctx.send({
message: 'okay',
posts: entity,
sanitizedPosts: sanitizeEntity(entity, { model: strapi.models.post })
}, 200);
}
You can solve it by making your own custom sanitize function which returns an array OR by processing the result before returning it like so:
let entity = await strapi.query('post').find();
let sanitizedEntity = sanitizeEntity(entity, { model: strapi.models.post });
//process sanitized results to an array
//return the result as array
I think this is a general Javascipt question however I am working in Vue Js, Laravel & Axios.
How do I push one JSON array into another JSON array? My problem is that when I push the secondary array into the primary array it is nested (see screenshot). I need it as part of the same array.
Is this a simple index issue? I've read concat can achieve this, but I have a "load more" button and wish to append the array and increase the size from the bottom, so push is appropriate. concat creates a new array and replaces current, which isn't desired as I want to maintain the existing array and just increase size for smoother loading of results, like on Pinterest scroll and the new results populated at the bottom.
(this is simplified code to get point across)
Data property empty array to start:
data () {
return {
articles: [],
}
},
Firstly, on page load the articles array is populated with JSON data.
created() {
axios.get(url)
.then(response => {
this.articles = response.data.data;
});
},
Then, I want to 'load more' results via method
loadMore() {
axios.get(url)
.then(response => {
console.log('article\'s array data')
console.log(this.articles)
this.articles.push(response.data.data)
console.log('new response array data')
console.log(response.data.data)
console.log('the updated array with pushed response array')
console.log(this.articles)
});
}
console log
This should be an array of 5 + 5 = 10 for length of articles array if properly appended.
both responses are correct and matching json as I've got it working via concat to merge. But undesirable as it reloads entire array.
If you are against .concat, which is super a very straightforward and performant way, you may try the following:
const myArray = [1,3,4,5];
myArray.push(...[6,7,8,9);
console.log(myArray); // this will contain [1,2,3,4,5,6,7,8,9] now
Taken from advice from both #noa-dev and #Shilly above - I used concat as below. Response had to be set in a var for promise as didn't work without. Thanks.
var new_array = response.data.data
this.articles = this.articles.concat(new_array)
Explanation how Vue doesn't actually replace the array when using concat! :) https://v2.vuejs.org/v2/guide/list.html#Replacing-an-Array
I make a call to server and save some stuff, after successfull save I need to update the json array object I have with new values. Here is some of my code
[
{
"id": 1,
"uuid": "ce54acea-db20-4716-bceb-2f3deb1b2b86",
"name": null,
"order": 1,
"children": []
}
]
I got an array of object, they can be nested inside each other as well.
After I edit a specific object I can chose to save the new values to the server. After Save button is pressed and before I send data to server I store a reference to the object being edited. I store this refefence inside
selectNode = function(clickedNode) {
this.selectedNode = clickedNode
this.tempNode = _.clone(clickedNode)
}
I have a temp object here because the user can press Cancel button as well and that means any changes need to be discared as well. So I make a reference to the object being edited and then make tempNode object which I actually edit and upon Save push to server.
My problem is that after the successfull save I would like to update all values inside the reference object as well.
Right above I have just few values, but in my app every node will be have lots of values so I do not want to be doing this
this.http
.put("/api/manage/v1/"+this.tempNode.uuid, this.tempNode)
.map(res => res.json())
.subscribe((data) => {
this.selectedNode.name = this.tempNode.name
this.selectedNode.order = this.tempNode.order
... and so on, this list could get long and not manageable after while
}, (error) => {
console.log(error)
}
);
I also tried to do this in the same place replacing the above with
this.selectedNode = _.merge({}, this.selectedNode, this.tempNode);
but this destroys the reference and creates new object so the original object inside a json array never gets updated and ofc nothing gets then updated on the screen either.
Is there a way to automate this
this.selectedNode.name = this.tempNode.name
this.selectedNode.order = this.tempNode.order
this.selectedNode.etc= this.tempNode.etc
this.selectedNode.etc= this.tempNode.etc7
to one line of code where all values get copied to referenced object without destroying the reference with new object?
If you want to keep the reference, you can use standard Object.assign to assign all properties of tempNode to selectedNode without reassigning selectedNode:
.subscribe((data) => {
Object.assign(this.selectedNode, this.tempNode);
Object.assign (and _.merge, presumably) mutates the first argument, and you want mutation, not reassignment.
I have an angular 2 app and I've managed to set up an input on my page with an autocomplete that calls out to an api and does sever side filtering and returning of values. A lot like the tutorial found here.
Now, I'm trying a few more inputs to my page, but I don't need to filter those on the server side. That would be inefficient. Better to just get all the values when my component loads and then filter in the component. This is causing me no shortage of problems. I have an api call that returns 3 string arrays I need. I am getting those from an Angular service using the standard method like so:
getFormFilters(): Observable<FormFilterModel> {
return this._http.get(this.baseUrl+this.getFormFiltersPath)
.map(res => res.json() as FormFilterModel);
}
The model:
export interface FormFilterModel {
array1: string[];
array2: string[];
array3: string[];
}
This works fine, and I get my observable back. Where I'm stuck is how do I now filter these 3 arrays locally? I have my inputs wired up to my form controls just like I do with the server side filtering input. I can't figure out how to get to the actual arrays in my Observable to filter them. Here's where I'm at with that code:
this.filteredArray1$ = this.searchForm.controls.array1Search.valueChanges
.debounceTime(300)
.distinctUntilChanged()
.switchMap(term => //some filtering here of formFilterModel$ to return a subset of array1 based on input)
I can filter an array via RegExp just fine, but getting to the actual array in the observable is something I just can't seem to do.
Subscribe to your api call an store the result in the component. Then, filter the arrays in valuesChanges.map.
ngOnInit() {
this.getFormFilters().subscribe(formFilters => {
this.formFilters = formFilters;
});
this.filteredArray1$ = this.searchForm.controls.array1Search.valueChanges
.debounceTime(300)
.distinctUntilChanged()
.map(search => this.formFilters.array1.filter(value => matches(value, search)))
}
First of all you have to subscribe to the response and then filtered the object that is recieved.
formfilterData :FormfilterModel;
filteredArray1:stringp[]=[];
this.serviceOb.getFormFilters().subscribe((formfilterData)=>{
this.formfilterData=formfilterData;
});
this.formfilterData.array1.filter((data)=>{
this.formfilterData.push(data);
})