[] vs [{...}] in browser tools, while both having same objects - javascript

If you look at the picture both arrays consist of same kind of object. first I create it with empty data as placeholder, but second one I create it with data coming from server.
writeValue(v: any) {
console.log('aaa');
console.log(v);
console.log('aaa');
this.form = new FormArray([]);
for (const value of v) {
console.log('bbb');
console.log(value);
console.log('bbb');
this.form.push(new FormControl(value));
}
this.form.valueChanges.subscribe(res => {
if (this.onChange) {
this.onChange(this.form.value);
}
});
}
for first case it goes through all of the writeValue code, for second one it doesn't go through the for(const values of v) code. why is this happening? when I print them out they seem to be the same other than one difference [{...}] vs [] in browser tools.
If you want to see how I create them. the first one is routes and the second one is routeslocal. I put them in angular formcontrol, and thats how it gets to writeValue via controlvalueaccessor. If you want to know how it works you could check my previous question here. there is more code, but it doesn't include the service.
ngOnInit() {
const routes: any[] = [];
routes.push({ ...dataI });
this.requestForm = this.fb.group({
statusId: null,
requestVehicles: this.fb.array([
this.fb.group({
garageId: 0,
routes: new FormControl(routes),
endDateTime: 0,
})
])
});
if (this.data.isEdit) {
this.Title = 'Edit';
this.data.fService.getRequest(this.data.requestId).subscribe(thisRequest => {
this.requestForm = this.fb.group({
statusId: thisRequest.status,
requestVehicles: this.fb.array([
])
});
thisRequest.requestVehicles.forEach((element, index) => {
const routeslocal: any[] = [];
element.routes.forEach((elementt, indexx) => {
this.data.fService.getAddressPoint(elementt).subscribe(sbed => {
const newRoute = {
addressPointId: sbed.addressPointId,
municipalityId: sbed.municipalityId,
regionId: sbed.regionId,
rvId: element.rvId,
sequenceNumber: indexx,
settlementId: sbed.settlementId,
regionName: sbed.regionName,
municipalityName: sbed.municipalityName,
settlementName: sbed.settlementName,
description: sbed.description,
};
routeslocal.push({...newRoute});
});
});
this.requestVehicles.push(this.fb.group({
endDateTime: new Date(element.endDateTime),
garageId: element.garageId,
routes: new FormControl(routeslocal),
}));
});
});
});
});
}
}

The opening line, [] or [{}], is immediately drawn in the console.
In the case of [], there was nothing in the array at logging time, so the browser draw it as an empty array. But the data was present when you looked at it and clicked on the small triangle, later.
You can reproduce this behavior with this code in your console:
;(function(){ let arr=[]; setTimeout(()=>{ arr[0] = {b:3}; }); return arr;})()
So the difference you saw is related to the (a)synchronicity of array filling.

Vato, you has two functions in your service:getRequest(requestId) and getAddressPoint(requestVehicles). The idea is return a whole object. You can create the function in the own service or in the component. I'd like in the service, and that return an objservable. You must use forkJoin and swithMap So . It's for me impossible check if work
**Update, see the stackblitz
getFullRequest(id): Observable<any> {
return this.getRequest(id).pipe(
switchMap((request: any) => {
//here you has the request. We create an array of observables
return forkJoin(
request.requestVehicles.map(
(r: any) => this.getAddressPoint(r))).pipe(map((res: any[]) => {
res.forEach((x: any, index: number) => {
x.sequenceNumber = index
})
return {
statusId: request.statusID,
routes: res
}
})
)
}))
}
then, in your component
if (this.data.isEdit) {
this.Title = 'Edit';
this.data.fService.getFullRequest(this.data.requestId).subscribe(thisRequest => {
this.requestForm = this.fb.group({
statusId: thisRequest.status,
requestVehicles: thisRequest.routes
});
Update 2 briefly explain about switchMap and forkJoin.
When we make this.getRequest(id) we received in request an object. In this object we has in requestVehicles an array (can be an array of objects or an array of numbers -or strings-). With each element of this array we can make a call, But instead of make the calls one to one, we want to make all these together. For this we use forkJoin. forkJoin received an array of observables and, in subscribe received the response in an array
//if we has an observable like:
getValue(id:number):Observable<any>{
return of({one:id})
}
//and an array like
myArray=[1,2]
//and an array of response whe we can store the responses
response:any[]
//we can do
for (let id of myArray)
{
this.getValue(id).susbcribe(res=>{
this.response.push(res)
})
}
//or
observables:any[]
for (let id of myArray)
{
this.observables.push(this.getValue(id))
}
forkJoin(this.observables).subscribe((res;any[])=>{
//in res[0] we have the result of this.getValue(1)
//in res[1] we have the result of this.getValue(2)
//so, simply
this.response=res
})
//or in a compact way
//with each element of the array
observables=myArray.map(x=>this.getValues(x))
forkJoin(this.observables).subscribe((res;any[])=>{
this.response=res
})
Well, there are two problems more. We want add a new propertie "sequenceNumber" to all the response. So we use res.forEach(...) to add the property. And we want return an object with somes properties of our original request (statusID) and in "routes" the array with the response. So we use map to transform the response. In our simple example above
//not return simple {one:1}
//return {id:1,one:1}
getResponse(2).pipe.map(res=>{
return {
id:1,
one:res.one
}
}

Related

Updating value inside nested object of object in React Native

I have state set as follow
const [stories, setStories] = useState([]);
I fetch Data from API in array, and i map the array and set the using setStories as:
setStories(prevState => prevState.concat({user: {name: 'XYZ', profile: 'ABC', stories: [{id: 1, image: 'testing'}];
The above codes are working fine, but i am stuck, when i have to concat the latest story if the id did not matched with fetched data. I have tried below solution but it didnot help:
stories.map(story => {
if(story && story.hasOwnProperty(key)){
//where above key is the user key fetched from the another API, i.e., user key
story?.[key].stories.map(storedStory =>
id(storedStory.id !== fetchedStory.id){
story?.[key].stories.concat({story})}
but the above code did not work, as it only mutate the state and is avoiding re-rendering.
Looking for a clean and efficient method to overcome this. THanks
It's hard to tell what you're trying to accomplish without seeing a full example. But I think your main problem is that you're not using the returned value from map, and from the naming it looks like you're appending the wrong element.
It will help to simplify first.
const newState = stories.map(story => {
if (story?.hasOwnProperty(key)) {
const found = story[key].stories.find(s => s.id === fetchedStory.id);
if (found) {
return story;
} else {
// Let's make a new object with the fetchedStory
// appended into THIS user's stories
return {
...story,
[key]: {
...story[key],
stories: [
...story[key].stories,
// This is supposed to be fetchedStory
// not `story` right??
fetchedStory,
]
}
}
}
} else {
return story;
}
});
setStory(newState);
Edit: You're having a hard time expressing your business logic, and the complexity of the data structure is not helping. So keep simplifying, encapsulate the complex syntax into functions then express your business logic plainly. Ie,
const appendStory = (originalObject, userId, storyToAppend) => {
return {
...originalObject,
[userId]: {
...originalObject[userId],
stories: [
...originalObject[userId].stories,
storyToAppend,
]
}
}
};
const userExistsInList = (users, user) => {
return users?.hasOwnProperty(user);
}
const newStoryAlreadyInStories = (stories, newStory) => {
return stories.find(s => s.id === newStory.id);
}
const newState = stories.map(story => {
if (userExistsInList(story, key)) {
const found = newStoryAlreadyInStories(story[key].stories, fetchedStory);
if (found) {
// User is already in state and the new story is already in the list
// Add business logic here
} else {
// User is already in state and the new story
// is not in their list
// Add business logic here
}
} else {
// User is not in the list yet
// Add business logic here
}
});

Match object inside object in jest

I'm testing a function to see if, when called, it will return the proper created list.
To start, I create the elements, using the createDesign.execute() functions. It's tested on another file and working.
Then, I call the function I want to test: listAllDesigns.execute() and store it's value in a variable.
If I console.log(list), it returns the full list properly.
In pseudocode, what I'd like to do is: Expect list array to have an element with the design object and, within it, a design_id that equals "payload3".
How should I write this test?
Is there a better way to do this? (other than checking if list !== empty, please)
it('should return a list of all designs', async () => {
// Create fake payloads
const payload1 = {
...defaultPayload,
...{ design: { ...defaultPayload.design, design_id: 'payload1' } },
};
const payload2 = {
...defaultPayload,
...{ design: { ...defaultPayload.design, design_id: 'payload2' } },
};
const payload3 = {
...defaultPayload,
...{ design: { ...defaultPayload.design, design_id: 'payload3' } },
};
await createDesign.execute(payload1);
await createDesign.execute(payload2);
await createDesign.execute(payload3);
const list = await listAllDesigns.execute();
// expect(list). ????
});
The easiest method would be a combination of expect.arrayContaining and expect.objectContaining like so:
expect(list).toEqual(
expect.arrayContaining([
expect.objectContaining({
design: expect.objectContaining({
design_id: "payload3"
})
})
])
);

Pushing responses of axios request into array

I have been pounding my head against this problem, and need help with a solution. I have an array of IDs within a JSON, and I am iterating over that array and making a GET request of each ID. I want to then push the response of those GET requests into an array.
Here is the function I am using to push the registrations into the array. It is iterating through an array of IDs:
getRegistrations = async (event) => {
let registrations = [];
await event.registrations.forEach(registration => axios.get('/event/getRegistration', {
params: {
id: registration
}
}).then(res => {
registrations.push(res.data.properties)
}
).catch(err => console.log(err)));
return registrations;
};
Here is where I am calling that code:
render() {
let event = this.getEvent();
let registrations2 = [{
age: 19,
bio: 'test',
firstName: 'hello',
lastName: 'bye',
password: 'adadas',
telephone: "4920210213"
}];
if (this.props.listOfEvents.events.length !== 0 && !this.props.listOfEvents.gettingList && event) { //check if the array is empty and list has not been rendered yet
let columns = [];
let registrations = this.getRegistrations(event);
console.log(registrations);
let eventProperties = event.properties[0];
Object.keys(eventProperties).forEach(key => columns.push({
title: eventProperties[key].title,
dataIndex: key,
key: key
}));
console.log(registrations);
console.log(registrations2);
return (
<h1>hi</h1>
)
}
return <Loading/>
}
When I console-log 'registrations' vs 'registrations2' they should be very identical. However, in the javascript console on Google Chrome, 'registrations appears as '[]' where 'registrations2' appears as '[{...}]'.
I know that it is an issue related to promises (I am returning the registrations array before actually pushing) but I have no idea how to fix it! Some friendly help would be very much appreciated!
I recommend Promise.all, it will resolve single Promise after all promises have resolved. And technically async function is also promise so it will return promise.
here the example.
https://codesandbox.io/s/jzz1ko5l73?fontsize=14
You need to use componentDidMount()lifecycle method for proper execution and state to store the data.
constructor (props) {
super(props);
this.state = {registrations :[]}
}
componentDidMount () {
let response = this.getRegistrations()
this.setState({registrations : response});
}
Then access that state in render method. It's not good practice to call api from render mothod.
Since getRegistrations(event) returns a promise, you should perform operations on its return value inside then.
Instead of
let registrations = this.getRegistrations(event);
console.log(registrations);
Do this
this.getRegistrations(event).then(registrations => {
console.log(registrations);
// other operations on registrations
});

How to use RxJS observables in sequential order?

Here's the deal: I have a HTTP get request that returns a JSON list of objects. Using RxJS I subscribe to receive the data of that list. Now, for each of the objects in that list I want to perform another HTTP request and then place the results of that request in an Array.
So far I've been able to do this but I can't seem to figure out how to maintain the order of the initial list with data. This probably has to do with the fact that the whole Observable mechanism is asynchronous.
Here's my code:
ngOnInit(): void {
this.shiftInformationService.getShifts("2016-11-03T06:00:00Z", "2016-11-06T06:00:00Z")
.subscribe(shifts => {
shifts.forEach(shift => {
this.addDataToAreaChart(shift.startDateTime, shift.endDateTime, shift.description);
});
});
}
addDataToAreaChart(startDate: string, endDate: string, description: string) {
this.machineStatisticsService
.getCumulativeMachineStateDurations(startDate, endDate)
.subscribe(s => {
this.areaChartData = [];
this.areaChartData.push(new AreaChartData(startDate, endDate, description, s));
});
}
What I want is to maintain the order of calling from the shifts.forEach loop when pushing the data in the areaChartData array.
Any ideas? Help would be appreciated!
UPDATE: SOLVED!
Final code:
ngOnInit(): void {
var startDate = new Date();
startDate.setDate(startDate.getDate() - 3);
this.shiftInformationService.getShifts(DateUtil.formatDate(startDate), DateUtil.formatDate(new Date()))
.subscribe(shifts => {
Observable.from(shifts)
.concatMap((shift) => {
return this.machineStatisticsService
.getCumulativeMachineStateDurations(shift.startDateTime, shift.endDateTime)
.map((result) => {
return {
"addDataToAreaChartValue": result,
"shift": shift
}
});
})
.subscribe(s => {
this.areaChartData = [];
this.areaChartData.push(
new AreaChartData(
s.shift.startDateTime,
s.shift.endDateTime,
s.shift.description + ' ' + s.shift.startDateTime.slice(5, 10),
s.addDataToAreaChartValue
)
);
});
});
}
Thanks to Michael!
Use concatMap to process in sequence.
Projects each source value to an Observable which is merged in the output Observable, in a serialized fashion waiting for each one to complete before merging the next.
Use map to append/transform value in observable.
Applies a given project function to each value emitted by the source Observable, and emits the resulting values as an Observable.
So, you need to do this
ngOnInit(): void {
this.shiftInformationService.getShifts("2016-11-03T06:00:00Z", "2016-11-06T06:00:00Z")
.subscribe(shifts => {
Rx.Observable.from(shifts) // create observable of each value in array
.concatMap((shift) => { // process in sequence
return this.addDataToAreaChart(
shift.startDateTime,
shift.endDateTime,
shift.description
).map((result) => {
return {
"addDataToAreaChartValue" : result, // addDataToAreaChart result
"shift": shift // append shift object here, so we can access it on subscribe
}
});
})
.subscribe(s => {
//this.areaChartData = []; // why??
this.areaChartData.push(
new AreaChartData(
s.shift.startDate,
s.shift.endDate,
s.shift.description,
s.addDataToAreaChartValue
)
);
});
});
}
addDataToAreaChart(startDate: string, endDate: string, description: string) {
return this.machineStatisticsService
getCumulativeMachineStateDurations(startDate, endDate);
}

Transform an array of strings to an array of objects with two intermediary $.getJSON calls

Input: an array of username strings
Needed output: an array of Javascript Objects that correspond to each username in the input. The properties for these JS objects is to be built from two API calls for each username (I am using $.getJSON calls. Suggestions welcome).
I have an array of usernames for the Twitch API:
let users = ["OgamingSC2", "storbeck", "comster404"] // actual list is longer
I want to use the Array.prototype.map() higher order function to create an array of objects like
let userObjects = [ {...}, {...}, {...}, ... ]
where each object looks like:
{
username: 'storbeck' // <-- Added by me
stream: null, // <-- Added by first API call
_links: { // <-- Added by first API call
self:'https://api.twitch.tv/kraken/streams/storbeck',
channel:'https://api.twitch.tv/kraken/channels/storbeck'
}
logo: 'http:// ... png' // <-- Added by second API call
}
These are the two API call functions that return the $.getJSON Promises:
let getStreamInfo = (username) => {
return $.getJSON('https://api.twitch.tv/kraken/streams/'+username+'?callback=?')
.then((x) => x) // should I include this then?
}
let getUserInfo = (twitchObject) => {
return $.getJSON('https://api.twitch.tv/kraken/users/'+ twitchObject.user )
}
What I have so far in my code, which isn't resulting in the intended objects is:
let userObjects = users.map((user)=>{
return getStreamInfo(user)
.done((data) => {
let result = {
username: user,
data: data
}
console.log(JSON.stringify(result)) // prints out the intended object so far
return result
})
})
Now when I print out the contents of userObjects, I get:
"{}"
"{}"
"{}"
// ... and so on
Going further, I'd like to chain userObjects and add more to each JS object from whatever I get in the getUserInfo function.
I'd like to go into how this can be done with functional Javascript, but this isn't necessary.
You are on the right way, you need only small edit on your functions.
let getStreamInfo = (username) => {
return $.getJSON('https://api.twitch.tv/kraken/streams/'+username+'?callback=?');
}
let getUserInfo = (user) => {
return $.getJSON('https://api.twitch.tv/kraken/users/'+ user);
}
let userObjects = [];
The core function instead needs Promise synchronization:
users.map((user)=>{
Promise.all(getStreamInfo(user), getUserInfo(user)).then((data)=>{
let obj = {
username: user,
stream: data[0].stream,
_links: data[0]._links,
logo: data[1].logo
}
userObjects.push(obj);
});
});

Categories