I am receiving an Observable back from an Angular service and I can see the data coming back from the service when it resolves using .subscribe
returnedObservable$.subscribe(res => {
console.log(res);
return res;
]);
I have tried assigning this to an Object inside of the .subscribe but the assignment isn't happening.
let newObj = {};
returnedObservable$.subscribe(res => newObj = res);
How can I make this assignment to get the values out of the Observable?
The first time it tries to access the response the result is null but eventually resolves.
The console.log(res) looks like this:
{
itemList: [
{ id: 1, name: Steve },
{ id: 2, name: Bob },
{ id: 3, name: Tim }
]
}
You can use it like this:
let newObj = {};
returnedObservable$.subscribe(res => {newObj = res}).add(()=>
{
//Use your object here
console.log(newObj);
});
Related
For the following code block:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach((item, i) => {
item = {
...item,
...changes
}
})
console.log(items) // items NOT reassigned with changes
items.forEach((item, i) => {
items[i] = {
...item,
...changes
}
});
console.log(items) // items reassigned with changes
Why does reassigning the values right on the element iteration not change the objects in the array?
item = {
...item,
...changes
}
but changing it by accessing it with the index does change the objects in the array?
items2[i] = {
...item,
...changes
}
And what is the best way to update objects in an array? Is items2[i] ideal?
Say no to param reassign!
This is a sort of a fundamental understanding of higher level languages like JavaScript.
Function parameters are temporary containers of a given value.
Hence any "reassigning" will not change the original value.
For example look at the example below.
let importantObject = {
hello: "world"
}
// We are just reassigning the function parameter
function tryUpdateObjectByParamReassign(parameter) {
parameter = {
...parameter,
updated: "object"
}
}
tryUpdateObjectByParamReassign(importantObject)
console.log("When tryUpdateObjectByParamReassign the object is not updated");
console.log(importantObject);
As you can see when you re-assign a parameter the original value will not be touched. There is even a nice Lint rule since this is a heavily bug prone area.
Mutation will work here, but ....
However if you "mutate" the variable this will work.
let importantObject = {
hello: "world"
}
// When we mutate the returned object since we are mutating the object the updates will be shown
function tryUpdateObjectByObjectMutation(parameter) {
parameter["updated"] = "object"
}
tryUpdateObjectByObjectMutation(importantObject)
console.log("When tryUpdateObjectByObjectMutation the object is updated");
console.log(importantObject);
So coming back to your code snippet. In a foreach loop what happens is a "function call" per each array item where the array item is passed in as a parameter. So similar to above what will work here is as mutation.
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach((item, i) => {
// Object assign just copies an object into another object
Object.assign(item, changes);
})
console.log(items)
But, it's better to avoid mutation!
It's better not mutate since this can lead to even more bugs. A better approach would be to use map and get a brand new collection of objects.
const items = [{
id: 1,
name: 'one'
},
{
id: 2,
name: 'two'
},
];
const changes = {
name: 'hello'
}
const updatedItems = items.map((item, i) => {
return {
...item,
...changes
}
})
console.log({
items
})
console.log({
updatedItems
})
As the MDN page for forEach says:
forEach() executes the callbackFn function once for each array
element; unlike map() or reduce() it always returns the value
undefined and is not chainable. The typical use case is to execute
side effects at the end of a chain.
Have a look here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
This means that although you did create new object for item, it was not returned as a value for that index of array. Unlike your second example, the first one is not changing original array, but just creates new objects and returns undefined. This is why your array is not modified.
I'd go with a classic Object.assign for this:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach( (item) => Object.assign(item,changes) )
console.log(items)
Properties in the target object are overwritten by properties in the sources if they have the same key. Later sources' properties overwrite earlier ones.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
The other approach you can take is to use map and create a new array based on the original data and the changes:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
const newItems = items.map((item) => {
...item,
...changes
})
console.log(newItems);
But if you need to modify the original array, it's either accessing the elements by index, or Object.assign. Attempting to assign the value directly using the = operator doesn't work because the item argument is passed to the callback by value not by reference - you're not updating the object the array is pointing at.
I'm attempting to re-map an Object that comes from an API call.
The format of the response is the following:
data: {
foo: [{
id: "1",
name: "joe",
info: "whatever"
}, {
id: "2",
name: "anna",
info: "whatever"
},...],
bar: [...]
}
The code I'm using to re-map the object inside each response array is:
const DATA = response.data;
const entries = Object.entries(DATA);
for (const entry of entries) {
entry[1].map(entry => ({
id: entry["id"],
mixed_info: entry["name"] + ", " + entry["info"]
}));
}
When I console.log the data after this, it shows the same as the initial response, as if it completly ignored the map function.
What am I doing wrong? Thanks for the attention.
map returns a new array, you are ignoring the result of the call.
Assign the result of the map call:
entry[1] = entry[1].map(...);
Array.prototype.map returns a new array - it doesn't modify the original.
You have to reassign the entry:
entry[1] = entry[1].map(/*...*/);
However that will only get reflected to the array inside entries, it won't change DATA. To change that, you have to either turn the key value pairs back into an object at the end:
DATA = Object.fromEntries(entries);
Or you have to reassign the DATA properties while iterating:
DATA[ entry[0] ] = entry[1].map(/*...*/);
I'd do:
const { data } = response;
for(const [key, value] of Object.entries(data)) {
data[key] = value.map(/*...*/);
}
let result = {};
for (const entry of entries) {
result[entry[0]] = entry[1].map(entry => ({
id: entry.id,
mixed_info: `${entry["name"] }, ${ entry["info"]}`,
}));
}
'result' contains exact remapped object.
In my post request I need to pass an array with an object inside it.
when I tried to add new properties inside an object its adding.
but when I tried to add when an object is present inside an array its not adding.
I have sportsvalues as array const sportsValues = [{ ...values }];
I am trying to build something like this, so that I can pass in the api
[
{
"playerName": 3,
"playerHeight": 1
}
]
can you tell me how to fix it.
providing my code snippet below.
export function sports(values) {
const sportsValues = [{ ...values }];
sportsValues.push(playerName:'3');
console.log("sportsValues--->", sportsValues);
// sportsValues.playerName = 3//'';
// sportsValues.playerHeight = 1//'';
console.log("after addition sportsValues--->", sportsValues);
console.log("after deletion sportsValues--->", sportsValues);
return dispatch => {
axios
.post(`${url}/sport`, sportsValues)
.then(() => {
return;
})
.catch(error => {
alert(`Error\n${error}`);
});
};
}
Since sportsValues is an array of objects, you can push new object into it. Check out code below.
const sportsValues = [];
sportsValues.push({
playerName:'3',
playerHeight: 1,
});
console.log(sportsValues);
I don't fully understand what you're trying to do, but here's some pointers:
If you're trying to update the object that's inside the array, you first have to select the object inside the array, then update it's attribute:
sportsValues[0].playerName = 3
although, I recommend building the object correctly first, then passing it to the array, it makes it a little easier to understand in my opinion:
const sportsValues = [];
const firstValue = { ...values };
firstValue.playerName = '3';
sportsValues.push(firstValue);
or
const firstValue = { ...values };
firstValue.playerName = '3';
const sportsValues = [firstValue];
or
const sportsValues = [{
...values,
playername: '3',
}];
if you're trying to add a new object to the array, you can do this:
const sportsValues = [{ ...values }];
sportsValues.push({ playerName: '3' });
etc...
Array.push adds a new item to the array, so in your code, you're going to have 2 items because you assign 1 item at the beginning and then push a new item:
const ar = [];
// []
ar.push('item');
// ['item']
ar.push({ text: 'item 2' });
// ['item', { text: 'item 2' }]
etc...
export function sports(values) {
const sportsValues = [{ ...values }];
sportsValues.push(playerName:'3');
let playerName='3'
sportsValues.playerName= playerName; // you can bind in this way
console.log("sportsValues--->", sportsValues);
return dispatch => {
axios
.post(`${url}/sport`, sportsValues)
.then(() => {
return;
})
.catch(error => {
alert(`Error\n${error}`);
});
};
}
service.$post() my observable from a service of return type <Observable<any[]>>, emits this collection
[{ name: 'nike' }, { name: 'nika' }, { name: 'niko' }, { name: 'niky' }]
this.posts = <Observable<any[]>>this.service.$post();
// result
// [{ name: 'nike' }, { name: 'nika' }, { name: 'niko' }, { name: 'niky' }]
My question is how can I control or filter the number of objects being returned from emitted collection, in the example below I want to take 2 objects from <Observable<any[]>>this.service.$post(). Please help
this.posts = this.service.$post()
.pipe(
take(2)
);
// should be
// [{ name: 'nike' }, { name: 'nika' }]
You can get the first element like this:
this.posts = this.service.$post()
.pipe(
map(arr => arr[0])
);
take(1) operator would return only the first value emitted by your observable. In this case it would be the entire array. And since the post request only emits once, it wouldn't really be useful.
If you want to keep x number of items, you can do it like this:
const x = 2;
this.posts = this.service.$post()
.pipe(
map(arr => arr.slice(0, x))
);
const x = 2;
return this.service.$post()
.map(arr => arr.slice(0, x));
I think this will do
I want to have a function with default parameters inside nested objects, and I want to be able to call it either f() or specifying only individual parameters.
// A function with nested objects with default parameters:
function f({ a = 1, callback = ({ name, param } = { name: "qwe", param: 123 }) } = {}) {
console.log("a:", a);
console.log("callback:", callback);
}
// And I want to run it like this:
f();
f({ callback: { params: "456" } });
// But 'callback.name' becomes undefined.
When destructuring is mixed with default parameters, I admit the code is hard to read and write (especially when there are nested objects...).
But I think you are trying to do that:
function f({callback: {name = "cbFunction", params = "123"} = {}} = {}) {
console.log(name);
console.log(params);
}
f();
f({callback: {params: '789'}});
I found none of the answers here to be what he wanted. But it IS actually possible in a somewhat sexy way by doing this:
(EDIT: Simplified syntax and also show how to add default values for subobjects)
function f({
a = 1,
callback = {}
} = {}) {
callback = { // default values
name: "cbFunction",
params: "123",
...callback // overwrites it with given values
}
// do the same for any subobjects
callback.subObject = {
arg1: 'hi',
arg2: 'hello',
...callback.subObject
}
console.log("a:", a)
console.log("callback:", callback)
}
f()
f({a: 2, callback: {params: '789', subObject: {arg2: 'goodbye'}}})
Turned out to call it like this solves the problem, but is it the best way?
function f({
a = 1,
callback = ({
name,
param
} = {
name: "qwe",
param: 123
})
} = {}) {
console.log("a:", a);
console.log("callback:", callback);
}
f();
f({ callback: { name, params: "456" } });
Answer by #Badacadabra is nearly correct but missing the other top level parameter specified in the question.
function f({a = 1, callback: {name = "qwe", params = "123"} = {}} = {}) {
console.log(a);
console.log(name);
console.log(params);
}
However note that within the function body the properties of callback are addressed without the containing object. You could reconstitute them into such an object if you wanted with the line:
const callback = { name, params }
Either way, from the invocation point this works to fill in all missing values from all levels such as:
f({a: 2})
f({a: 2, callback: { name: "abc"}})
f({a: 2, callback: { params: "456" }})
etc.
EDIT
In response to Joakim's comment:
TotalAMD also said in a comment that "I want to use several nested objects with same fields name". So if he tries that approach with callback1 and callback2 as arguments then he would have to use different field names in them.
I missed that original requirement. One way to maintain the desired, duplicated nested names within the function interface would be to alias them within the scope of the function, as follows:
function f({
a = 1,
callback1: {name: name1 = "abc", params: params1 = "123"} = {},
callback2: {name: name2 = "def", params: params2 = "456"} = {},
} = {}) {
console.log(a);
console.log(name1);
console.log(params1);
console.log(name2);
console.log(params2);
}
You can then call the function with the designed interface and expected results:
f ({ callback1: { name: "One" }, callback2: { name: "Two" } })
Caveat: Whilst technically possible and potentially useful, this could get messy at deeper nesting levels. It might then be worth looking for an alternative interface design with less indirection.