I am a newbie in angular 5 and I am making a simple cart to learn angular 5. I am stuck in a situation that I am confuse that How to check the duplicate entry in the cart data. Actually the problem is that I am confuse about should I store objects in array or array in objects to store data.
This is what I am doing
Home component
import { Component, OnInit } from '#angular/core';
import { DataService } from '../data.service';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
items: Array<object> = [];
total_items:Number = 0;
cart = {};
broadcast_obj = {items:[],totals:{}};
total_sum:Number = 0.0;
htmlToAdd:String = '';
constructor(private _data: DataService) { }
ngOnInit() {
//this.items_count = this.cart.length;
this._data.cast.subscribe(res => this.broadcast_obj = res);
this._data.changeCart(this.broadcast_obj);
}
additem(id,itemText,amount){
this.total_items = 10;
this.total_sum += amount;
this.cart = {id:id, name: itemText, price: amount,quantity:1};
if(this.items.length>0){
this.items.find(x => x.id == 3);//error id does not exist on type object
}
this.items.push(this.cart);
this.broadcast_obj.items = this.items;
this.broadcast_obj.totals = {total_items:this.total_items,total_sum:this.total_sum};
console.log(this.broadcast_obj)
//this._data.changeCart(this.broadcast_obj);
}
}
I am storing data in 2 objects and pushing them into array
1- {id:id, name: itemText, price: amount,quantity:1};
2- {total_items:this.total_items,total_sum:this.total_sum};
Now I want to check if id exists then increase the quantity but I am confuse am i doing right because I am searching for id in the array object and it is showing error as show in comment(id does not exist on type object).
Here is the current structure of array of objects
I was also thinking that If I store objects in array indexes of their ids like
if item id=199 then I store object in array index[199] so that I can quickly search any item in the array.
I still dont know which approach is better as of searching point of view or both are wrong.
Please resolve my error as well as help me to store cart data in correct structure so that I can search item quickly and also pass cart data in observable.
Thanks.
You are getting the error because of this line: items: Array<object> = []; This liine says that items is an array of Objects (javascript objects). Objects don't have properties like id. You need to create an interface for you item:
interface ICartItem {
id: number;
name: string;
price: number;
quantity: number;
}
Then in your component you can do items: ICartItem[] = []; (same as items: Array<ICartItem> = [];) This will make the error go away.
Your component:
// ...
items: ICartItem[] = [];
cart: ICartItem; // no need to initialise it with empty object
//...
I agree with the accepted answer from #BorisLobanov, I just thought that I would add a second example of how else you could achieve this...
By defining items as: items: Array<any> = new Array<any>(); You will also mitigate these errors.
However as stated by #BorisLobanov, of which I agree ,note that:
Using any is not a very good practice, it makes Typescript kinda pointless
... In my opinion one place where you would use an array of type any is when the "items" are being derived from an API call. The reason for this is that you may receive an array of objects which contain say 100 properties. However you only want to use 4 of the properties... or for the example shown below where you need the data to reach a certain value, but don't actually care about the data itself.
The use of any prevents the typescript compiler (no errors at compile time) from verifying the type structure of items, which in turn will allow you to unsafely access properties you know will exist.
The benefit is definitely apparent when the data structure has nested objects, for example:
australianCities = [
{
name: 'Sydney',
suburbs: [{
name: 'town 1',
houses: [{
population: 3,
address: 'aa'
},
{
population: 1,
address: 'bb'
}
]
}]
}, ...
];
It already hurts my soul to think about defining all these interfaces for every object here (imagine one which is actually complex), especially when API's can change, if all I want to do sum the total population in all the cities in Australia.
However this can be way less cumbersome by using any... something like:
let sum = (accumulator, currentValue) => accumulator + currentValue;
population = australianCities.map(city => city.towns.map(town => town.houses.map(house => house.population).reduce(sum)).reduce(sum)).reduce(sum);
var australianCities = [{
name: 'Sydney',
suburbs: [{
name: 'town 1',
houses: [{
population: 3,
address: 'aa'
},
{
population: 1,
address: 'bb'
}
]
}]
},
{
name: 'Perth',
suburbs: [{
name: 'town 1',
houses: [{
population: 10,
address: 'aa'
},
{
population: 2,
address: 'bb'
}
]
}]
}
];
var sum = (accumulator, currentValue) => accumulator + currentValue;
totalPopulation = australianCities.map(city => city.suburbs.map(town => town.houses.map(house => house.population).reduce(sum)).reduce(sum)).reduce(sum);
console.log({
totalPopulation
});
Related
I have two models in Objection - "brands" and "offers".
Brand:
const { Model } = require('objection')
class Brand extends Model {
static get tableName() {
return 'brands'
}
...
static get relationMappings() {
const Offer = require('./offer-model')
return {
offer: {
relation: Model.HasManyRelation,
modelClass: Offer,
join: { from: 'brands.id', to: 'offers.brand_id' }
}
}
}
}
Offer:
const { Model } = require('objection')
class Offer extends Model {
static get tableName() {
return 'offers'
}
}
A brand has many offers, but I want to get brands which have at least 1 offer using withGraphFetched, excluding brands which have no offers. Here's what I have so far:
const brandModel = this.objection.models.brand
const query = brandModel.query().withGraphFetched('offer')
query.page(page, page_size)
const offers = await query
This returns the "joined" data, but also returns brands which don't have offers. For example:
[{
id:1,
name: 'brand 1',
offers: [{offerId: 1, offerName: 'offer 1'}]
},{
id:2,
name: 'brand 2',
offers: []
}]
In the above data, I don't want the brand with ID 2 to be in the result set.
I am using Objection/Knex to paginate the results, so I can't just exclude the brands with empty object arrays after the query has been executed.
I can achieve this using raw queries, but that means I can't use the Objection dynamic attributes and a few other key parts of Objection.
Thanks!
You can just tack a whereExists onto the query; something like
const query = brandModel.query()
.withGraphFetched('offer')
.whereExists(
(qb) => qb.select('id').from('offers')
.where('offers.brand_id', knex.ref('brands.id'))
);
Even though the whereExists bit is directly Knex, the query still goes through your models so stuff you've defined there should still apply (maybe unless you're doing something very wild that directly affects the columns used inside the whereExists)
I have a reduce function that is building multiple levels and is working perfectly for me except for one issue
Currently, it's building data based on employee first, then by date, area, and job. I'm getting all of the data at the proper level but I'm now trying to aggregate certain data for a totals section at the date level and it's just listing values rather than aggregating.
Basically, in the line I've notated below, I'd like to create a value called total_scans that simply adds up ALL scans for any orders on that date. In other words, for the record for Miranda on 8/12 I would expect the total_scans at the date level to have 49 as the value. Am I on the right track?
const nest = (rows) =>
rows.reduce(
(a, row) => {
const employee = a[row.employee] || (a[row.employee] = { dates: {} })
const date = employee.dates[row.job_date] || (employee.dates[row.job_date] = { areas: {} })
const order = date.areas[row.area_number] || (date.areas[row.area_number] = { jobs: {} })
const job = order.jobs[row.job] || (order.jobs[row.job] = { hours: '', scans: '', job_date: '' })
job.hours += row.hours
job.scans += row.scans
job.job_date = row.job_date
//this line is my issue
date.total_scans += job.scans
return a
},
{}
);
new Vue({
el: "#app",
props: {
},
data: {
rows: [
{
employee: "Miranda",
job: "123",
hours: "10",
job_date: "08/12/2021",
scans: 37,
area_number: "1234567",
},
{
employee: "Miranda",
job: "167",
hours: "15",
scans: 12,
job_date: "08/12/2021",
area_number: "1234568",
},
{
employee: "Miranda",
job: "184",
hours: "18",
scans: 24,
job_date: "08/13/2021",
area_number: "1234569",
}
],
},
computed: {
numbersByEmployee() {
return nest(this.rows)
},
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
{{numbersByEmployee}}
</div>
Your usage of reduce is a little irregular. The idea of reduce is to take an iterable (array) and return a single value, usually something like a String or Number.
Also, you're causing all sorts of side effects in your reducer, by modifying the object and arrays. Since Javascript is pass-by-reference for arrays and objects, those changes you're causing will be reflected in the original object, which is not how Vue prescribes things are done. If you want to modify data, it should be done in a watch, not a computed.
Finally, I believe you're overcomplicating your reduce function. Instead of a long reduce like that, you could simply do the below. Note the initialValue of 0.
const nest = (rows) =>
rows.reduce(
(sum, row) => {
return sum + row['scans'];
},
0
);
Obviously this will count all scans. If you want to only count scans by date, how about save yourself running the reducer across the array, and instead run filter first? Something like
const nest = (rows) =>
rows.filter(({job_date}) => job_date === SomeDate).reduce(...)
The {job_date} is a destructuring assignment. You could also split out a date filtered array into its own computed.
I am doing some filtering using React Context and I am having some difficulty in updating a child's array value when a filter is selected.
I want to be able to filter by a minimum price, which is selected in a dropdown by the user, I then dispatch an action to store that in the reducers state, however, when I try and update an inner array (homes: []) that lives inside the developments array (which is populated with data on load), I seem to wipe out the existing data which was outside the inner array?
In a nutshell, I need to be able to maintain the existing developments array, and filter out by price within the homes array, I have provided a copy of my example code before, please let me know if I have explained this well enough!
export const initialState = {
priceRange: {
min: null
},
developments: []
};
// Once populated on load, the developments array (in the initialState object)
// will have a structure like this,
// I want to be able to filter the developments by price which is found below
developments: [
name: 'Foo',
location: 'Bar',
distance: 'xxx miles',
homes: [
{
name: 'Foo',
price: 100000
},
{
name: 'Bar',
price: 200000
}
]
]
case 'MIN_PRICE':
return {
...state,
priceRange: {
...state.priceRange,
min: action.payload
},
developments: [
...state.developments.map(development => {
// Something here is causing it to break I believe?
development.homes.filter(house => house.price < action.payload);
})
]
};
<Select onChange={event=>
dropdownContext.dispatch({ type: 'MIN_PRICE' payload: event.value }) } />
You have to separate homes from the other properties, then you can apply the filter and rebuild a development object:
return = {
...state,
priceRange: {
...state.priceRange,
min: action.payload
},
developments: state.developments.map(({homes, ...other}) => {
return {
...other,
homes: homes.filter(house => house.price < action.payload)
}
})
}
In my React state, I have the state:
this.state = {
users: [{
id: 1,
name: "john",
age: 27
}, {
id: 2,
name: "ed",
age: 18
}, {
id: 3,
name: "mel",
age: 20
}]
}
I am rendering the name correctly. When you click on the name, it should remove the name, which will need an onClick that takes in a function and that returns a removeUser function.
It is my understanding that you do not want to mutate the state in React, but return a new state. So, in my removeUser function, I did:
removeUser(index) {
// Making a new copy of the array
const users = [...this.state.people].splice(index, 1);
this.setState({ users });
}
I bind my method with this.removeUser = this.removeUser.bind(this).
When I tested out my code, I am removing the users as expected. However, when I run my code against a test that my friend wrote, I got a failed test that said: Expected 1 to be 0
That message tells me that I must be mutating the state somehow, but I am not sure how. Am I returning a new array and updating the state correctly? Can someone explain to me how I should update my state correctly in this case?
Here is the full code:
class Group extends Component {
constructor(props) {
super(props);
this.state = {
users: [
{id: 1, name: "john", age: 27},
{id: 2, name: "ed", age: 18},
{id: 3, name: "mel", age: 20}
]
}
this.removeUser = this.removeUser.bind(this);
}
removeUser(index) {
const users = [...this.state.users].splice(index, 1);
this.setState({ users });
}
render() {
const list = this.state.users.map((user, i) => {
return (
<div onClick={() => this.removeUser(i)} key={i}>{user.name}</div>
);
});
return (
<div>
{list}
</div>
);
}
}
you could also change your onClick and pass it the user.id instead of the index. that would allow you to filter the results instead of needing to splice the array.
onClick={() => this.removeUser(user.id)}
removeUser(id) {
const users = this.state.users.filter((user) => user.id !== id);
this.setState({ users });
}
const users = [...this.state.users].splice(index, 1)
looks like it should be removing an item from a collection, which I suppose it technically is. The problem is that users doesn't contain the list of users you want to keep.
Instead, splice has modified your new array in-place and then returned the removed items:
splice docs
Return value
An array containing the deleted elements. If only one element is removed, an array of one element is returned. If no elements are removed, an empty array is returned.
Instead, if you'd like to use splice, create a new array:
const users = [...this.state.users]
and then splice the new array:
users.splice(index, 1)
You'll run into a similar issue if you need to sort an array.
The issue of modifying data in-place is generally frowned upon for react, in favor of immutable references. This is because React uses a lot of direct comparison of objects as a heuristic approach to speed things up. If your function modifies an existing object instance, React will assume the objects haven't changed.
The act of copying to a new object and then operating on the data comes with some tradeoffs. For removing a single item, it's negligible. For removing many items, you may be better served by an alternative method, such as multiple slice calls:
const users = [
...this.state.users.slice(0, index)
...this.state.users.slice(index + 1)
]
However this too is quite verbose.
Another approach is to use an immutable variant of splice:
// this quick example doesn't handle negative start indices
const splice = (start, deleteCount, ...items) => arr => {
const output = []
let i
for (i = 0; i < start && i < arr.length; i++) {
output.push(arr[i])
}
output.push(...items)
for (i += deleteCount; i < arr.length; i++) {
output.push(arr[i])
}
return output
}
const users = splice(index, 1)(this.state.users)
You should use slice() instead of splice() because splice() mutates the original array, but slice() returns a new array. Slice() is a pure function. Pure is better!!
removeUser(index) {
const users = [
...this.state.users.slice(0, index),
...this.state.users.slice(index+1)
];
this.setState({ users });
}
Here is JS Fiddle
So, I've been working on making an APP in React Native for which i have programmed a RESTFul API in Java, which returns some data in JSON format. I will have a datastructure that looks something like this - it is also the initial state for this Reducer, I have simply deleted some of the values as they are irrelevant:
categories: [{
id: 1,
name: '',
image: '',
subcategories: [
{
name: '',
id: 1,
products: [{
name: '',
description: 'This is a good product',
id: 55,
quantity: 4
}, {
name: '',
description: 'This is a good product',
id: 3,
quantity: 0
}]
},
{
name: '',
id: 2,
products: [{
name: '',
description: 'This is a good product',
id: 4,
quantity: 0
}]
}]
}, {
id: 2,
name: '',
image: '',
subcategories: [
{
name: '',
id: 3,
products: [{
name: '',
description: 'This is a good product',
id: 15,
quantity: 0
}]
}
]
}]
I will be saving this in my Redux store but where i struggle is when I have to update the quantity of a certain product with only the products id.
So far I've found a solution using immutable.js but it is quite ugly and I'm really unsure if this is the way to go.
I've searched for solutions but have not yet found one with a solution without normalizing the datastructure. For now I want to see if I can avoid normalizing the data, as I want to keep the same format for posting stuff back to the server. (and for learning purposes)
My solution in my Reducer with immutable.js looks like this:
case types.ADD_PRODUCT:
const oldState = fromJS(state).toMap();
var findCategoryIndex = oldState.get('categories').findIndex(function (category) {
return category.get("subcategories").find(function (subcategories) {
return subcategories.get("products").find(function (product) {
return product.get("id") === action.productId;
})
})
})
var findSubcategoryIndex = oldState.getIn(['categories', findCategoryIndex.toString()]).get('subcategories').findIndex(function (subcategory) {
return subcategory.get("products").find(function (product) {
return product.get("id") === action.productId;
});
})
var findProductIndex = oldState.getIn(['categories', findCategoryIndex.toString(), 'subcategories', findSubcategoryIndex.toString()]).get('products').findIndex(function (product) {
return product.get("id") === action.productId;
})
var newState = oldState.setIn(['categories', findCategoryIndex.toString(),
'subcategories', findSubcategoryIndex.toString(), 'products', findProductIndex.toString(), 'quantity'],
oldState.getIn(['categories', findCategoryIndex.toString(), 'subcategories', findSubcategoryIndex.toString(), 'products', findProductIndex.toString(), 'quantity'])+1);
const newStateJS = newState.toJS();
return {...state, categories: [...newStateJS.categories]}
I know all this may seem overcomplicated for the case, but I am simply trying to learn different approaches to this as I am very new to everything that has to do with JavaScript.
I am not looking for optimization on the data format itself, but I am looking for ways to manipulate data in nested arrays in Redux
I hope to get some good feedback on this and hopefully find out if I am on the right track :)
EDIT: It works with spread operators aswell without using Immutable.js, but I don't really understand what the difference is. Is there a performance difference and why choose one over the other?
case types.ADD_PRODUCT:
return {
...state,
categories:[...state.categories.map(category => ({...category,
subcategories:[...category.subcategories.map(subcategory => ({...subcategory,
products:[...subcategory.products.map(product => product.id === action.productId ? {...product, quantity: product.quantity+1} : product)]}))]}))]
}
When things the data to become a bit too deep, you can still use helper like Immutable.js Map. I am not sure this is the correct way to use Immutable.js since I am also experimenting it. It lets you return new state in a less verbose way like this :
import { fromJS } from 'immutable';
export default function reducer(state = initialState, action) {
const iState = fromJS(state);// Immutable State
switch (action.type) {
case type.ACTION:
return iState.setIn(['depth1','depth2', 'depth3'], 'foobar').toJS()
// return a copy of the state in JavaScript
// {depth1: {depth2: {depth3: 'foobar'} } }
default:
return state;
}
and from what I heard, using Immutable is more performant but it adds an another dependency to your project. Careful tough, if you are using combineReducers(reducers), it expects to be pass a plain object, so be sure to pass a plain object and not an Immutable object.
You said you are not specifically looking for optimization on the data format itself. Correct me if I am wrong, I think normalizr will help you to gain in flatness and be easier to update your store