How to watch for a model's deep changes, from templates? - javascript

I have a model on my scope, which is an object of objects. I have seen this, but I want to do this from the template as I have a filter defined on it.
var App = angular.module('app', []);
App.controller('myCtrl', function($scope) {
$scope.items = {
{ name: 'Cricket bat', cost: '2500', quantity: 0},
{ name: 'Football', cost: '1100', quantity: 0}
};
$scope.cartItems = {}; // This holds the items. I want quantity of each item separately so it's not an array.
I have defined a filter getPrice which calculates the price for the items in users cart.
And I have in the template:
{{ cartItems | getPrice }}
Is it possible to have the template update after any of the nested object value (ie. quantity of one of the items from the cart) changes? If yes, how?

Move the quantity field to the cartItem object
$scope.items = {
{ name: 'Cricket bat', cost: '2500'},
{ name: 'Football', cost: '1100'}
};
$scope.cartItems = {
{ name: 'Cricket bat', quantity: 3},
{ name: 'Football', quantity: 6}
};
Then update your getprice filter based on the above json. This should take care of your cost in the cart getting updated when a price of an item changes.

Seems like your strategy is not good overall.
An "object of objects" as you mean it { {id:1}, {id:2} } is just not valid javascript, and will generate an error. Choose an array of objects [ {id:1}, {id:2} ] or a true object { 1: {id:1}, 2: {id:2} }
#Blackhole is right, you don't need to watch anything, interpolation is part of angular.js core and does all that tricky watch stuff for you. Defining a cartItems variable, and interpoling it (with brackets {}) in a template, filtering it or not, will keep the interpolated value in sync with the variable value without you doing anything more. It's even two-ways bound (if you change model, variable will also change). You can verify the sync by removing temporiraly your filter : try to put {cartItems} in your template and update your cart, you'll see the template updated.
This model format would probably be more appropriate
$scope.items = [
{ name: 'Cricket bat', cost: '2500' },
{ name: 'Football', cost: '1100' }
];
$scope.cartItems = [
{ item: { name: 'Cricket bat', cost: '2500' }, quantity: 3 },
];

Related

How do I incorporate the index of an array while defining a property of said array within a class in JavaScript?

Sorry if the title makes no sense.. let me explain
Say I have the following 2d array.. the first array representing ice cream and the second representing milkshakes
menu = [ ['vanilla', 'chocolate', 'almond'],
['vanilla', 'pineapple', 'strawberry'] ]
Now I create a class that takes this array as input
class cafe{
constructor(menu){
this.iceCreams = menu[0]
this.milkshakes = menu[1]
}
}
Now I want to define a property called 'price' for each flavor of milkshake.
this.milkshakes[n].price = < a function that computes price based on value of n >
so that i can access them like this :
cafe.milkshakes[0].price
So how do I incorporate the index 'n' of the array while defining the property
I haven't tried anything bcos I dont know how to even approach this ☚ī¸
You can do it in your constructor.
You can grab the names, and call map function on it and do whatever you want. Please check the following example. There, calculatePrice is a function that takes the index and returns the price based on the index.
class Cafe {
constructor (menu) {
this.iceCreams = menu[0].map((flavor, index) => {
return {
flavor,
price: calculatePrice(index)
}
});
this.milkshakes = menu[1].map((flavor, index) => {
return {
flavor,
price: calculatePrice(index)
}
});
}
This is a minimal answer.
UPDATE:
For a detailed and improved answer: https://codesandbox.io/s/cafe-example-wxp2c4
So, in the milkshakes array you need each item as an object data structure, not a string.
menu = [ ['vanilla', 'chocolate', 'almond'],
[{ flavor: 'vanilla' }, { flavor: 'pineapple' }, { flavor: 'strawberry' }] ]
and then you can loop through and set the price, something like this.
menu.milkshakes.forEach((item, index) => item.price = index))
you can use objects:
menu = [
[
{
name: "vanilla",
price: 200,
},
{
name: "chocolate",
price: 200,
},
{
name: "almond",
price: 200,
},
],
[
{
name: "vanilla",
price: 200,
},
{
name: "pineapple",
price: 200,
},
{
name: "strawberry",
price: 200,
},
],
];
and then:
class cafe{
constructor(menu){
this.iceCreams = menu[0]
this.milkshakes = menu[1]
}
}
now iceCreams and milshakes have the property price and name
example:
iceCreams[n].price
iceCreams[n].name

Javascript object as a type of mini database?

Is it possible to use a JavaScript object as a type of mini database? I often find myself needing a kind of database structure when I'm coding in JS but it feels like overkill to use an actual database like MySQL (or similar).
As an example, let's say I need to structure this data as a JS object:
Object idea: Stuff to sell
Items to sell: The junk in the garage
Object structure: List all items including item name, item condition, and item value
In order to make this into a JS object I would maybe write:
var stuffToSell = {};
Then my first item would maybe look like:
var stuffToSell = {
item : "Coffee Maker",
condition : "Good",
price : 5
};
Now to me this seems like I'm on the right track, until I come to add another item and I end up having to use the properties item, condition, and price again in the same JS object — which feels wrong? — or is it?? At this point my brain keeps shouting the word "ARRAY!" at me but I just can't see how I can use an array inside the object, or an object inside an array to achieve what I want.
My end goal (in this simplified example) is to be able to then use object-oriented syntax to be able to access certain items and find out specific information about the item such as price, condition etc. For example if I want to know the price of the "coffee maker" I would like to write something like:
stuffToSell["coffee maker"].price
...and then the result above should be 5.
I feel like I'm on the right track but I think I'm missing the array part? Could someone please tell me what I'm missing or maybe what I'm doing completely wrong! And also if it is wrong to have duplicate property names in the same JS object? For example, is it okay to have:
var stuffToSell = {
item : "Coffee Maker",
price : 5,
item : "Mountain Bike",
price : 10,
item : "26 inch TV",
price : 15
};
...it seems wrong because then how does JS know which price goes with which item??
Thanks in advance :)
You're definitely on the right track!
A lot of people will refer to what you're talking about as a hash.
Here's my suggested structure for you:
var store = {
coffee_maker: {
id: 'coffee_maker',
description: "The last coffee maker you'll ever need!",
price: 5,
},
mountain_bike: {
id: 'mountain_bike',
description: 'The fastest mountain bike around!',
price: 10,
},
tv: {
id: 'tv',
description: 'A big 26 inch TV',
price: 15,
},
}
Having a structure like that will let you do this:
store.mountain_bike.price // gives me 10
Need an array instead, say for filtering or looping over?
Object.keys gives you an Array of all the object's keys in the store ['coffee_maker', 'mountain_bike', 'tv']
// Now we just have an array of objects
// [{id: 'coffee_maker', price: 5}, {id: 'mountain_bike', price: 10} ...etc]
var arr = Object.keys(store).map(el => store[el])
Need to just filter for items that are less than 10?
This will give us an array of products less than 10:
// gives us [{id: 'coffee_maker', price: 5}]
var productsUnder10 = arr.filter(el => el.price < 10)
These techniques can be chained:
var productsOver10 = Object.keys(store)
.map(el => store[el])
.filter(el => el.price > 10)
Need to add a product?
store['new_product'] = {
id: 'new_product',
description: 'The Newest Product',
price: 9000,
}
Here's another way, which would be good to start getting used to.
This is a 'safe' way to update the store, read up on immutability in javascript to learn about it
store = Object.assign({}, store, {
'new_product': {
id: 'new_product',
description: 'The Newest Product',
price: 9000,
}
})
...and another way, that you should also read up on and start using:
This is the object spread operator, basically just an easier way to work with immutable structures
store = {
...store,
'new_product': {
id: 'new_product',
description: 'The Newest Product',
price: 9000,
}
}
Resources
JavaScript Arrow Functions
Object and Array Spread Syntax
Immutable Javascript using ES6 and beyond
You can actually use json or create an array of objects.If using a separate file to store the objects, first load the file. Use array filter method to get an new array which matches the filter condition , like you want to get the item with id 1. This will return an array of objects.
var dict = [{
'id': 1,
'name': 'coffee-mug',
'price': 60
},
{
'id': 2,
'name': 'pen',
'price': 2
}
]
function getItemPrice(itemId) {
var getItem = dict.filter(function(item) {
return item.id === itemId
});
return getItem[0].price;
}
console.log(getItemPrice(1))
JSON objects don't support repeated keys, so you need to set unique keys.
Put an id as your key to group your items:
var stuffToSell = {
'1': {
item: "Coffee Maker",
price: 5
},
'2': {
item: "Mountain Bike",
price: 10
}
.
.
.
}
Now you can access the item's price very fast.
Look at this code snippet (Known Ids)
var stuffToSell = {
'1': {
item: "Coffee Maker",
price: 5
},
'2': {
item: "Mountain Bike",
price: 10
},
'3': {
item: "26 inch TV",
price: 15
}
};
let getPrice = (id) => stuffToSell[id].price;
console.log(getPrice('1'));
See? the access to your items it's fast and your code follows a readable structure.
Look at this code snippet (Item's name as key)
var stuffToSell = {
'Coffee Maker': {
price: 5
},
'Mountain Bike': {
price: 10
},
'26 inch TV': {
price: 15
}
};
let getPrice = (id) => stuffToSell[id].price;
console.log(getPrice('Coffee Maker'));
Look at this code snippet (Item's name: price)
var stuffToSell = {
'Coffee Maker': 5,
'Mountain Bike': 10,
'26 inch TV': 15
};
let getPrice = (id) => stuffToSell[id];
console.log(getPrice('Coffee Maker'));

Angular - can I keep two arrays separated and use them in an ng-repeat?

first of all, this is an extension of this question here: Angularjs map array to another array
Currently, I have two scopes:
$scope.products = [
{id: 001, name: "prod 1", ...},
{id: 002, name: "prod 2", ...},
{id: 003, name: "prod 3", ...},
{id: 004, name: "prod 4", ...},
{id: 005, name: "prod 5", ...}
]
$scope.cart = {
products: [001,002]
...
}
$scope.products contains a list of all the available products with the products information, while $scope.cart.products contain a list of IDs added to the cart.
The answer above explains how to merge the two arrays together, however, I'd like to keep them separate and create a sort of map between the two. Is it possible? Is a custom filter in the repeater the best option here, or there's a built-in way to do this in angular? thanks for any suggestion
EDIT
Filter:
filter('mapProducts', function($filter) {
return function(products, ids) {
var result;
result = [];
$filter('filter')(products, function(p) {
if (ids.indexOf(p.id) !== -1) {
return result.push(p);
}
});
return result;
};
});
and in the repeater:
<div ng-repeat="product in products | mapProducts:cart.products">
You don't get much benefit by not creating a new array. You can use an Angular filter to "map" the results, and that might be cleaner than doing the same thing in the controller, but behind the scenes Angular is actually just creating a new sub-array. Even if you make a custom filter, at the end of the day you are creating a new array.
angular.module('myApp').
filter('idNumber', function() {
return function(products,idNumbers) {
var newArray= [];
newArray = products.filter(function(p) {
return idNumbers.find(function(i) {
return i == p.id;
});
})
return newArray;
}
});
Additionally, if you use the view version of the filter:
ng-repeat="product in products | myFilter: cart.products"
There could be a performance hit, depending on how big your array is:
The filter can be applied in the view template with markup like {{ctrl.array | filter:'a'}}, which would do a fulltext search for "a". However, using a filter in a view template will reevaluate the filter on every digest, which can be costly if the array is big.
That means that you would want to use the filter in the controller:
var newArray = $filter($scope.products, 'myFilter', cart.products)
Which also means you would be creating a new array.
You can have $scope.cart.products be an array of references to objects in $scope.products:
$scope.products = [
{id: 001, name: "prod 1", ...},
{id: 002, name: "prod 2", ...},
{id: 003, name: "prod 3", ...},
{id: 004, name: "prod 4", ...},
{id: 005, name: "prod 5", ...}
];
$scope.cart = {
products: []
};
$scope.addProduct = function(product) {
$scope.cart.products.push(product);
}
You can see that they are the same object reference:
$scope.addProduct($scope.products[1]);
// Returns true
$scope.cart.products[0] === $scope.products[1]

Can I set value of an object by accessing other values in this object?

I am new in React.js, I am designing a little game.
When I am setting the life value, I want to use the value of job, can I do it in React.js?
Here is part of my code:
this.setState({
players:[
{
id:uuid.v4(),
name: "A",
job: "Wizard",
life: this.getRandHealth("Wizard")
}, {
id:uuid.v4(),
name: "B",
job: "Witch",
life: this.getRandHealth("Witch")
}]});
I want directly access job instead of signing them manually. Can I do it?
A solution is to define the players list without the life and then use .map() to add it:
var list = [{
id: uuid.v4(),
name: "A",
job: "Wizard",
}, {
id: uuid.v4(),
name: "B",
job: "Witch",
}];
this.setState({
players: list.map(function(obj) {
return {
id: obj.id,
name: obj.name,
job: obj.job,
life: this.getRandHealth(obj.job)
};
}, this);
});
If the id is always uuid.v4() then you can move that into the map as well and reduce the repetition further.

Angular.js: How do I use ng-bind to display concat. elements of an array as a string?

I am new to Angular and have a basic question about ng-bind that I couldn't find in the documentation. My scenario is based the shopping cart app in the O'Reily Angular.js book and I cannot seem to get ng-bind to work.
Desired output: I need to modify my controller function so I can show my updated $scope.items array elements in a 'Grand Total' span.
Here is the function:
function CartController($scope) {
$scope.items = [
{title: 'Software', quantity: 1, price: 1399.95},
{title: 'Data Package (1TB)', quantity: 1, price: 719.95},
{title: 'Consulting (per hr.)', quantity: 1, price: 75.00}
];
$scope.remove = function(index) {
$scope.items.splice(index, 1);
},
$scope.reset = function(index) {
$scope.items = [
{title: 'Software', quantity: 0, price: 1399.95},
{title: 'Data Package (1TB)', quantity: 0, price: 719.95},
{title: 'Consulting (per hr.)', quantity: 0, price: 75.00}
];
};
}
I would recommend making a grandTotal function on your $scope and then binding that, like this:
http://jsfiddle.net/XMTQC/
HTML
<div ng-app ng-controller="CartController">
Grand Total: <span>{{grandTotal()}}</span>
<br/>
Grand Total: <span ng-bind="grandTotal()"></span>
<br/>
</div>
JavaScript
$scope.grandTotal = function () {
return $scope.items.reduce(function (p, c) {
return p.price || p + c.price;
});
};
You can also use interpolation (instead of ngBind) as indicated in the first span.

Categories

Resources