Printing shopping cart total price - arrays

Hello I am trying to teach myself angular framework.
I have built a simple shopping cart where the products can be added to the basket by using a tutorial. The problem I am having is that I can't seem to get the total price to show. any one who could show me the direction to go in or what to do would be greatly appreciated. At the moment my total cost function has the same function as the total number of objects as price so I need to change the total cost function but wouldn't know what to attempt.
I have shared my full project below rather than paste a lot of code.
also two bonus questions, currently I have an array of items. how would I approach removing the items and calling them from an API instead?
and how would i add a 10% discount should the user have three or more items in the basket?

shopping-cart.component.ts
import { Component, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'shopping-cart',
template: `
<h1>Shopping Cart ({{products.length}})</h1>
<h2>Total Cost £{{calcTotal()}}</h2>
<cart-product *ngFor="let product of products" [product]="product" (productRemoved)="removeProduct($event)"><cart-product>
`,
})
export class ShoppingCartComponent {
#Input() products: any[] = [];
#Output() productRemoved = new EventEmitter();
calcTotal() {
if (!this.isArrayEmpty()) {
if (this.products.length < 2) {
const targetProduct = this.products[0];
return targetProduct.price * targetProduct.num;
} else {
return this.products.reduce((a, b) => (a.num * a.price) + (b.num * b.price));
}
} else {
return 0;
}
}
isArrayEmpty(): boolean {
return this.products.length < 1;
}
removeProduct(product) {
this.productRemoved.emit(product)
}
}
app.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
productList = [
{name: 'Array Item One', price: 10},
{name: 'Array Item Two', price: 20},
{name: 'Array Item Three', price: 30}
];
cartProductList = [];
addProductToCart(product) {
const productExistInCart = this.cartProductList.find((productInCart) => productInCart.name === product.name);
if (!productExistInCart) {
this.cartProductList.push({...product, num:1});
return;
}
productExistInCart.num += 1;
console.log(this.cartProductList);
}
removeProduct(product) {
this.cartProductList = this.cartProductList.filter(({name}) => name !== product.name)
}
}

If you want to show the total price, you will just have to introduce some changes to calcPrice methode inside shopping-cart.component.ts.
instead of just returning the number of products, you return the number of products (for each product) multiplied by the price of each product, hence prod.price * prod.num
just like the following:
calcPrice() {
return this.products.reduce((acc, prod) => acc+= prod.price * prod.num ,0)
}
If you want to add a discount to your total price, you will have to introduce more change to calcPrice methode again.
instead of returning the total price of products inside the cart, you first check if there is 3 or more products in it and apply the discount if true, otherwise, if false, your just return the normal price with no discount.
just like the following:
calcPrice() {
return this.products.reduce((acc, prod) => {
if (this.calcTotal() >= 3) {
// 0.9 means 90% of the total price which make it a 10% discount
return acc+= prod.price * prod.num * 0.9
} else {
return acc+= prod.price * prod.num
}
},0)
}
If you want to retrieve products from an API, you will have to use Angular's HttpClientModule, here you can find a link to a beginner friendly tutorial on how to use it.

Related

How to show the currency format in lwc tree grid

Hi i Created one lwc tree grid it is working fine showing the tree grid but one issue Iam not able to shown the number fields in the currency format it is showing $Nan.I have three columns in my lwc Treegrid
Column1 : Data Product Name,
Column2 : Current Value,
Column3 : Potential Value,
I want to show the current value and Potential Value in currency format
import { LightningElement,track,wire,api } from 'lwc';
import getDataProductRecordId from
'#salesforce/apex/FetchDataProductRecordId.getDataProductRecordId';
import SystemModstamp from '#salesforce/schema/Account.SystemModstamp';
export default class Data_products_tree_view extends LightningElement {
dataDomainsval;
error;
#track expandedRows = [];
#track gridData = [];
#track gridColumns = [{type:'url',
fieldName:'linkName',
label:'Data Product Name',
typeAttributes: {label: { fieldName: 'Name' }, target: '_blank'}
},{
type:'Number',
fieldName:'Data_Product_Valuation_Current__c',
label:'Current Value'
},
{
type:'Text',
fieldName:'Data_Product_Valuation_Potential__c',
label:'Potential Value'
}];
//#track gridData;
#track gridData2;
#api recordId;
connectedCallback(){
var formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
maximumFractionDigits: 0,
minimumFractionDigits: 0,
});
getDataProductRecordId({recId : this.recordId}).then(result =>{
var tempDomains = [];
for(var i=0;i<result.length;i++){
if(result[i].Parent__c == undefined){
tempDomains.push(JSON.parse(JSON.stringify(result[i])));
var arr = [];
for(let k=0;k<tempDomains.length;k++){
tempDomains[k]['linkName'] = '/' + tempDomains[k].Id;
tempDomains[k].Data_Product_Valuation_Current__c = formatter.format(tempDomains[k].Data_Product_Valuation_Current__c);
if(tempDomains[k].Data_Valuation_Components__r != undefined){
tempDomains[k]._children = tempDomains[k].Data_Valuation_Components__r;
for(let l=0;l<tempDomains[k]._children.length;l++){
console.log('childs update'+tempDomains[k]._children[l]);
tempDomains[k]._children[l]['linkName'] = '/' + tempDomains[k]._children[l].Id;
}
delete tempDomains[k].Data_Valuation_Components__r;
}
console.log('print my josn'+JSON.stringify(tempDomains[k].Data_Valuation_Components__r));
}
}
console.log('jsonvalue'+JSON.stringify(tempDomains));
}
this.gridData= tempDomains;
})
}}

Inconsistent behaviour of inputs values linked to an array in Angular

I am using an array of numbers in an Angular Component, linked to a series of inputs in the html template, using *ngFor. When a change is detected on a given input, the value of the corresponding cell in the array is updated. I checked it and the values of the array in the components are correctly updated.
However, a new value on a given input sometimes impact the value shown in another input (i.e. the new value is also written in that input). This usually happens with different inputs whose values are initially identical. It also seems to always be an input further in the loop that is impacted.
What is even more strange is that if I display the values of the array as plain text, the values shown are always correct.
I also manually set unique names and ids to the inputs in order to avoid any possible confusion, with no result.
I have made a small jsfiddle code showing the issue. The issue is immediately visible if you change the first input value to '2': the second input will also see its values changed to '2'.
The jsfiddle is using Angular 4, I have tested with Angular 6 and it doesn't work either.
Any idea what I might be doing wrong or where the issue is ?
let { Component, NgModule, OnInit } = ng.core;
#Component({
selector: 'my-app',
template: `
<div *ngFor="let v of values; let i = index;" style="float: left;"><input type="text" name="input-{{i}}" [value]="v" (change)="onChange(i, $event)"/><p>{{ v }}</p></div>
`,
})
class HomeComponent implements OnInit {
public values: number[] = [];
n = 5;
ngOnInit() {
for(let i = 0; i < this.n; i++)
this.values.push(1);
}
onChange(i: number, event)
{
this.values[i] = parseInt(event.target.value);
}
}
const { BrowserModule } = ng.platformBrowser;
#NgModule({
imports: [ BrowserModule ],
declarations: [ HomeComponent ],
bootstrap: [ HomeComponent ]
})
class AppModule { }
const { platformBrowserDynamic } = ng.platformBrowserDynamic;
platformBrowserDynamic().bootstrapModule(AppModule);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div *ngFor="let v of values; let i = index;" style="float: left;">
<input type="text" name="input-{{i}}" [value]="v" (change)="onChange(i, $event)"/>
<p>{{ v }}</p>
</div>
using (input) instead of (change) can working
let { Component, NgModule, OnInit } = ng.core;
#Component({
selector: 'my-app',
template: `
<div *ngFor="let v of values; let i = index;" style="float: left;"><input type="text" name="input-{{i}}" id="input-{{i}}" value="{{v}}" (input)="onChange($event,i)"/><p>{{ v }}</p></div>
`,
})
class HomeComponent implements OnInit {
public values: number[] = [];
n = 5;
ngOnInit() {
for(let i = 0; i < this.n; i++)
this.values.push(1);
}
// event first
onChange(event,i: number)
{
this.values[i] = parseInt(event.target.value);
console.log(event)
}
}
const { BrowserModule } = ng.platformBrowser;
#NgModule({
imports: [ BrowserModule ],
declarations: [ HomeComponent ],
bootstrap: [ HomeComponent ]
})
class AppModule { }
const { platformBrowserDynamic } = ng.platformBrowserDynamic;
platformBrowserDynamic().bootstrapModule(AppModule);
suggest using [(ngModel)] instead of value ,(ngModelChange) instead of (change),do not mixed

Angularjs addmore directive and scope management

I am trying to create a directive which has addmore button.
In the controller I have a tickets obj which is empty. I am trying to add new tickets (multiple) to this obj while clicking "Add Tickets".
What I need is
Each ticket should increment by 1.
The ticket price should be updated in tickets array when a price is
updated in input box (two way binding).
Should be able to update the quantity from controller or directive
which should update in quantity input (one way binding).
I have created a plunker , any help would be appreciated, have spend lot of time in this.
Fancy solution with components
I forked your plunker here with a component version.
When you can, I recommand you the use of components over directives.
I splitted it in two components, that's way easier to separate the functionnalities. One components for the list, and an other for one item representation.
tickets component
It handles the ng-repeat on components, the add button and the sum functions.
app.component('tickets', {
template: '<div ng-repeat="ticket in $ctrl.tickets">#{{ticket.id}}<ticket ng-change="$ctrl.recompute()" assign="$ctrl.assign" ng-model="ticket"/></div><hr><button ng-click="$ctrl.addOne()">Add One</button><br>Total price : {{$ctrl.price}}<br>Total quantity : {{$ctrl.quantities}}',
controller: class Ctrl {
$onInit() {
this.tickets = [];
this.price = 0;
this.quantities = 0;
}
count() {
return this.tickets.length;
}
addOne() {
this.tickets.push({
id: this.count(),
price: 0,
color: 'red',
quantity: 1
});
this.recompute();
}
recompute() {
this.price = 0;
this.quantities = 0;
this.tickets.forEach((ticket) => {
this.price += ticket.price;
this.quantities += ticket.quantity;
});
}
assign(ticket) {
console.log("ticket Assigned : ", ticket);
}
}
});
ticket component
It handles the representation and actions for one ticket.
app.component('ticket', {
template: '<div><label>Quantity</label><input type="number" ng-model="$ctrl.ticket.quantity" ng-change="$ctrl.ngChange"/><br><label>Price</label><input type="number" ng-model="$ctrl.ticket.price" ng-change="$ctrl.ngChange"/><br><button ng-click="$ctrl.assignMe()">Assign</button></div>',
bindings: {
ngModel: '=',
ngChange: '<',
assign: '<'
},
controller: class Ctrl {
$onInit() {
this.ticket = this.ngModel;
}
assignMe() {
this.assign(this.ticket);
}
addOne() {
this.tickets.push({
price: 0,
color: 'red',
quentity: 0
});
}
}
});
Let me now if you want a version with directive.

How to nest ng-repeat with two related entities

I have two entities:
OrderOpened
ProductOrdered with relation:
relationship ManyToOne {
ProductOrdered {Order} to OrderOpened
}
I need to list Orders with related Products in one view.
No problem with Orders:
<tr ng-repeat="orderOpened in vm.orderOpeneds track by orderOpened.id">
<td><a ui-sref="order-opened-detail({id:orderOpened.id})">{{orderOpened.id}}</a></td>
<td>
<a ui-sref="desk-detail({id:orderOpened.desk.id})">{{orderOpened.desk.description}}</a>
</td>
<td>{{orderOpened.openingTime | date:'shortTime'}}</td>
<td>{{orderOpened.user.login}}</td>
[...]
</tr>
But then I want to nest Products related to this Order like:
<tr ng-repeat="productOrdered in vm.productOrdereds track by productOrdered.id">
<td><a ui-sref="product-ordered-detail({id:productOrdered.id})">{{productOrdered.id}}</a></td>
<td>{{productOrdered.orderedTime | date:'medium'}}</td>
[...]
</tr>
Controller:
[...]
function loadAll() {
OrderOpened.query(function(result) {
vm.orderOpeneds = result; //works
angular.forEach(vm.orderOpeneds, function(productOrdered) {
TestOrderOpened.query({id:productOrdered.id}, function(result2) {
//in java: List<ProductOrdered> productOrdereds = productOrderedRepository.findAllByOrderId(id);
vm.productOrdereds = result2;
})
})
});
}
In result I get products related only to last iteration of angular.forEach, not related to current Order.
How can I pass productOrdered.id to ng-repeat directive or use another way to get related entities in one view?
The result you get is normal:
In your controller, you have 2 lists defined:
vm.orderOpeneds
vm.productOrdereds
With this, you can only display one list of productOrdereds, which happens to be the one of the last loaded order.
If you want to display the list of products for each order, you have to store them somewhere. Several solutions:
1) In each loaded order, store the related products, i.e.
function loadAll() {
OrderOpened.query(function(result) {
vm.orderOpeneds = result; //works
angular.forEach(vm.orderOpeneds, function(productOrdered) {
TestOrderOpened.query({id:productOrdered.id}, function(result2) {
//in java: List<ProductOrdered> productOrdereds = productOrderedRepository.findAllByOrderId(id);
// CHANGE: here store the products in the order
productOrdered.productOrdereds = result2;
})
})
});
}
2) Or buid a separate collection like a map whose keys are the order id and the values the list of associated products
var productsByOrderId = {}
function loadAll() {
OrderOpened.query(function(result) {
vm.orderOpeneds = result; //works
angular.forEach(vm.orderOpeneds, function(productOrdered) {
TestOrderOpened.query({id:productOrdered.id}, function(result2) {
//in java: List<ProductOrdered> productOrdereds = productOrderedRepository.findAllByOrderId(id);
// CHANGE: here store the products in the map
productsByOrderId[productOrdered.id] = result2;
})
})
});
}
You can bind product with your orders.
var results = [];
function loadAll() {
OrderOpened.query(function(result) {
vm.orderOpeneds = result; //works
angular.forEach(vm.orderOpeneds, function(productOrdered) {
TestOrderOpened.query({id:productOrdered.id}, function(result2) {
// bind your product with orders
productOrdered.productOrdereds = result2;
// then
results.push(productOrdered);
});
});
vm.orderOpeneds = results;
});
}

Get the total value of each item in an array with Angular 2

I have an array of shirts, each shirt has a price and I would like to get the total price of all the shirts using Angular 2.
This is what my data looks like:
shirts: [
{
name: 'shirt 1',
cost: '20'
},
{
name: 'shirt 2',
cost: '10'
},
{
name: 'shirt 3',
cost: '10'
}
]
I would like to then add the costs together and display in my component This is roughly where I have got to so far:
export class ShirtsListComponent {
shirts: Shirts;
constructor(#Inject(Shirts) csp: Promise<Shirts>, cdRef: ChangeDetectorRef) {
csp.then((cs) => {
this.shirts = cs;
cdRef.reattach();
});
for (var shirt in this.shirts) {
console.log(shirt);
}
}
}
Trouble is that console.log(shirt) returns nothing here and even if it did I am unsure how to then add the new shirt items together. Another element to this question is should I be doing this on the component or in the service?
Edit:
I made the following changes but I am getting 0 and I should be getting 40
html:
<ul class="list-group">
<li class="list-group-item" [shirtListItem]="shirt" *ngFor="#shirt of shirts"></li>
</ul>
<pre>{{totalShirts}}</pre>
Updated ShirtComponent:
export class ShirtListComponent {
shirts: Shirts;
totalShirts:number = 0;
constructor(#Inject(Shirts) csp: Promise<Shirts>, cdRef: ChangeDetectorRef) {
csp.then((cs) => {
this.shirts = cs;
cdRef.reattach();
this.shirts.forEach(s => this.totalShirts += s.cost );
});
}
}
Your for-loop is called before the promise is resolved.
You could do the for-loop inside the promise resolve function. like this:
export class ShirtsListComponent {
shirts: Shirts;
totalCost:number = 0;
constructor(#Inject(Shirts) csp: Promise<Shirts>, cdRef: ChangeDetectorRef) {
csp.then((cs) => {
this.shirts = cs;
cdRef.reattach();
this.shirts.forEach(s => this.totalCost += s.cost );
});
}
}
Update
Your Json structure has the cost property as a string type which should be edited to be of number type like this:
shirts: [
{
name: 'shirt 1',
cost: 20
}
...
]
Alternatively, you can change the line where you sum the cost to be:
this.shirts.forEach(s => this.totalCost += parseFloat(s.cost) );
About the other element of your question:
From angular2 website:
A service is typically a class with a narrow, well-defined purpose. It should do something specific and do it well.
Examples include:
- logging service
- data service
- message bus
- tax calculator
- application configuration
In relation to your question, for me, it makes sense to put the cost calculation on the service only if the total cost is always calculated including all shirts and not just part of the list.
Examples:
Total cost on service: if the total cost needs to be calculated only once probably after retrieving the shirts list on backend "it will also make more sense to move the calculation to the server backend".
Total cost on component: if you are only showing the total cost of the shirts currently showing on the page, the shirts currently added to the shopping cart, the shirts currently selected by user ... and so on

Resources