How to build an array using form values in Angular - arrays

I have the function in the component as below:
updateInfo(info: NgForm) {
const changedValues = Object.keys(info.controls)
.filter(key => info.controls[key].dirty === true)
.map(key => {
return { control: key, value: info.controls[key].value }
});
console.log(changedValues);
}
This function emits the changes form values, but now I want to build an array using changedValues as below:
{
firstName: "john",
lastName: "sam"
}
How could I implement it?
Edit: currently I'm getting like this in the console log
[
{firstName: "john"},
{lastName: "sam"}
]

Don't use map because that returns an array. Instead use forEach and add the items to your object:
updateInfo(info: NgForm) {
const changedValues = {};
Object.keys(info.controls).forEach(key => {
if (info.controls[key].dirty) {
changedValues[key] = info.controls[key].value;
}
});
console.log(changedValues);
}

Related

React: check if a data is duplicated in a map to ignore it

Is there any way to know if a data is repeated within a map? For example:
newArray = [name: "Jose", name:"Pedro", name:"Jose", name:"Ramon"]
newArray.map((questmapn: any, index: any) => ({questmapn.name}))
I need to know if questmapn.name is repeated inside the loop to create a ternary that doesn't show the duplicates. Is there a simplified way?
You can use onlyUnique function like below:
const newArray = [
{ name: "Jose" },
{ name: "Pedro" },
{ name: "Jose" },
{ name: "Ramon" }
];
function onlyUnique(repeatedArray) {
const names = [];
const uniqueArray = [];
repeatedArray.forEach((item) => {
if (!names.includes(item.name)) {
names.push(item.name);
uniqueArray.push(item);
}
});
return uniqueArray;
}
const uniqueArray = onlyUnique(newArray);
Note: Pass repeated array to this function and returned value will be unique based on name property.

Generic filter for X number of properties

I want to make a generic filter function. Currently I have a function that looks like this:
const filterRows = () => {
items.filter((item) => {
if(selectedDrinks.length > 0 && selectIds.length > 0) {
return selectedDrinks.includes(item.description) && selectedIds.includes(item.props.id)
}else if(selectedDrinks.length > 0) {
return selectedDrinks.includes(item.description)
}else if(selectedIds.length > 0) {
return selectedIds.includes(item.props.id)
}
}
}
The number of if checks I need to do will grow exponentially if I add one more thing to filter by.
I've made a pathetic try below. One issue I encountered is if I have a nested structure and want to access ["props/id"] as I don't know the syntax for it. Also tried ["props:id"] etc. And if I add multiple strings in the query it does not work either. And even if I could add multiple strings properly it would only work as an OR.
And for me it would be selectedDrinks && selectedId as both need to match for it to filter, not selectedDrinks || selectedIds
I want to include everything in both selectedDrinks and selectedIds as a query, and they should filter only if both are included in "assets" as description and props:id. I should also be able to add e.g "selectedNames" as a third "query parameter".
const selectedDrinks: string[] = [
"cola",
"fanta",
]
const selectedIds : string[] = [
"5",
"4",
]
interface s {
description: string;
name: string;
props: {
id: string
}
}
const items: s[] = [
{
description: "cola",
name: "computer",
props: {
id: "4"
}
},
{
description: "fanta",
name: "laptop",
props: {
id: "5"
}
},
{
description: "sprite",
name: "phone",
props: {
id: "6"
}
}
]
export function genericFilter<T>(
object: T,
filters: Array<keyof T>,
query: string[]
):boolean {
if(query.length === 0)
return true
return filters.some(filter => {
const value = object[filter]
console.log(value)
if(typeof value === "string") {
return value.toLowerCase().includes(query.map(q => q.toLowerCase()).join(""))
}
if(typeof value === "number") {
return value.toString().includes(query.map(q => q.toLowerCase()).join(""))
}
return false
})
}
const myFilterResult = items.filter((asset) => genericFilter(item, ["props", "name"], ["5"]))
console.log(myFilterResult)
If anyone is interested, here is how I solved it.
/**
*
* #returns A new list of filtered objects
* #param objects The objects that we want to filter
* #param properties The properties we want to apply on the object and compare with the query
* #param queries The queries we want to filter by
*/
export function genericFilter<T>(
objects: T[],
properties: Array<keyof T>,
queries: Array<string>[] | Array<number>[]
):T[] {
return objects.filter((object) => {
var count = 0;
properties.some((props) => {
const objectValue = object[props]
if(typeof objectValue === "string" || typeof objectValue === "number") {
queries.forEach((query) => {
query.forEach((queryValue) => {
if(queryValue === objectValue) {
count+=1;
}
})
})
}
})
return count === properties.length;
})
}
export default genericFilter;
How you call the function, can include X amount of filters and strings to search for.
const result = genericFilter(assets, ["description", "id", "name"], [selectedAssetTypes, selectedIds, selectedNames])

Adding to an array within an array state in ReactJS

I'm trying to add functionality for adding to a state, more specifically "list", in the following state:
shoeList : [
{name: 'Nike',
list : [
{type: 'boots', revenue: '1000000', gender: 'mens', price: '49.99', id: 3},
{type: 'shoes', revenue: '13280100', gender: 'womens', price: '99.99', id: 2}
]
}
],
Right now I have a component that displays a form for the user to enter new values for type revenue gender and price.
Here is the code for the component(not including the forms and text input html):
state = {
}
//when changes occur in text input fields
handleChange = (e) => {
this.setState({
[e.target.id]: e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault();
this.props.addShoe(this.state);
And in the root component i have the addShoe function:
addShoe = (shoe) => {
shoe.list.id = Math.random();
//returns a new array so no alteration of original array
let shoeList = [...this.state.shoeList, shoe];
this.setState({
shoeList: shoeList
})
}
Trying this code gives me an error saying shoe.list.id is undefined? Also I think I'm missing something to add in the component file specifically in the state. Also is there any way to directly access list like this.state.shoeList.list? I'm not sure if i have to add list to shoeList. Any help would be great thanks
In your example, if the intention is to add an item to specifically the Nike list:
addShoe = (shoe) => {
this.setState({
// return an altered copy of the array via map
shoeList: this.state.shoeList.map(brandItem => {
if (brandItem.name === 'Nike') {
return {
// use spread syntax for other object properties to persist
...brandItem,
list: [
// use spread syntax to keep the original items in the list
...brandItem.list,
{
// assuming shoe is an object without an id property
...shoe,
id: Math.random()
}
]
}
} else {
return brandItem;
}
})
}

Why i cannot update value of specific index in an array in react js via set State?

I have an array like below
[
1:false,
9:false,
15:false,
19:false,
20:true,
21:true
]
on click i have to change the value of specific index in an array.
To update value code is below.
OpenDropDown(num){
var tempToggle;
if ( this.state.isOpen[num] === false) {
tempToggle = true;
} else {
tempToggle = false;
}
const isOpenTemp = {...this.state.isOpen};
isOpenTemp[num] = tempToggle;
this.setState({isOpen:isOpenTemp}, function(){
console.log(this.state.isOpen);
});
}
but when i console an array it still shows old value, i have tried many cases but unable to debug.
This is working solution,
import React, { Component } from "react";
class Stack extends Component {
state = {
arr: [
{ id: "1", value: false },
{ id: "2", value: false },
{ id: "9", value: false },
{ id: "20", value: true },
{ id: "21", value: true }
]
};
OpenDropDown = event => {
let num = event.target.value;
const isOpenTemp = [...this.state.arr];
isOpenTemp.map(item => {
if (item.id === num) item.value = !item.value;
});
console.log(isOpenTemp);
this.setState({ arr: isOpenTemp });
};
render() {
let arr = this.state.arr;
return (
<React.Fragment>
<select onChange={this.OpenDropDown}>
{arr.map(item => (
<option value={item.id}>{item.id}</option>
))}
</select>
</React.Fragment>
);
}
}
export default Stack;
i hope it helps!
The problem is your array has several empty value. And functions like map, forEach will not loop through these items, then the index will not right.
You should format the isOpen before setState. Remove the empty value
const formattedIsOpen = this.state.isOpen.filter(e => e)
this.setState({isOpen: formattedIsOpen})
Or use Spread_syntax if you want to render all the empty item
[...this.state.isOpen].map(e => <div>{Your code here}</div>)

Why is the original item in my array being overwritten with Object.assign?

I have a unit test that is producing something I didn't expect:
Background: I'm making a simple todo list with Angular/test driven development.
Problem: When I call editTask on an item in the array, it changes the item's value. But, I don't see how it's changed in the original array because the original array is never accessed in the method I'm testing. Please help me connect HOW the original array is being changed? It seems Object.assign is doing this, but why?
describe('editTask', () => {
it('should update the task by id', () => {
const dummyTask1 = { id: 1, name: 'test', status: false };
service.tasks.push(dummyTask1); //refers to TestBed.get(TaskService)
const id = 1;
const values = { name: 'cat', status: false };
service.editTask(id, values);
console.log(service.tasks); // why does this log this object? [Object{id: 1, name: 'cat', status: false}]
expect(service.tasks[0].name).toEqual(values.name); // Test passes
});
});
Here is the method I'm testing:
editTask(id, values) {
const task = this.getTask(id);
if (!task) {
return;
}
Object.assign(task, values); //How does this line change the array?
return task;
}
getTask(id: number) {
return this.tasks.filter(task => task.id === id).pop(); //is this altering the original array somehow?
}
If needed, here's the full Angular service:
export class TaskService {
tasks: any = [];
lastId = 0;
constructor() { }
addTask(task) {
if (!task.id) {
task.id = this.lastId + 1;
}
this.tasks.push(task);
}
editTask(id, values) {
const task = this.getTask(id);
if (!task) {
return;
}
Object.assign(task, values);
return task;
}
deleteTask(id: number) {
this.tasks = this.tasks.filter(task => task.id !== id);
}
toggleStatus(task) {
const updatedTask = this.editTask(task.id, { status: !task.status});
return updatedTask;
}
getTasks() {
return of(this.tasks);
}
getTask(id: number) {
return this.tasks.filter(task => task.id === id).pop();
}
}
Here is the github repo: https://github.com/capozzic1/todo-tdd
The getTask() method is getting a reference to the item in the array using the array filter() method.
It then uses Object.assign() to change the properties of the item. The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target object.
So now the values of the reference in memory of the item is changed. Because it is a reference in memory you will see the original item being changed.

Resources