Tyler Tyler - 6 months ago 25
Javascript Question

ng-model within ng-repeat - Initialize Unique Scope Properties

I'm creating a shopping cart with angular 1.5.x and am really trying to focus on doing things the proper 'Angular Way.' I have a product list (

ng-repeat
), in which the user can adjust quantity of any item (
ng-model
), then add that item (and associated quantity) to their cart (
ng-click
).

The issue I am having is what variable to assign to ng-model in the quantity input. I don't wish to update the
ctrl.productList
array, so I figured I would create a seperate
ctrl.itemQuantity
primitive. However, with
ng-repeat
each input box then changes when one is changed.

My solution currently is to initialize a new primitive property with a dynamic name using
$index
in
ng-model
within the html (but not initialize it in the controller).

This works fine, but seems a bit hacky, and the inputs all start as blank rather than 0 because the
ng-model
begins undefined. If this is truly the way to go, I suppose I could initialize all of the
ng-model
variables by running a
forEach
on
ctrl.productsList
and assigning each to 0.

Does anyone have a better way to go about this? It seems like a fairly common situation.

Markup:

<div class="section-container product-container">
<div class="section-header product" ng-repeat="product in products.productList">
<div class="product-info">
<div>{{ product.name }} - <span>{{ product.price | currency }}</span></div>
<div><a href="">{{ product.brand }}</a></div>
</div>
<div class="product-selection">
<label for="product-quantity-{{$index}}">Quantity: </label>
//Initialize new dynamically named ng-model for each quantity input
<input id="product-quantity-{{$index}}" type="number" ng-model="products['quantity-' + $index]">
<a href="" class="product-cart-btn" ng-click="products.addCart(product, $index)">+</a>
</div>

</div>
</div>


Controller:

function ProductsController() {
var ctrl = this;

//Cart
ctrl.cartList = [];
ctrl.addCart = function(product, index){
if(ctrl.cartList.indexOf(product) === -1){
ctrl.errorMessage = '';
console.log(ctrl);
//Add input value here
product.quantity = ctrl['quantity-'+index];
ctrl.cartList.push(product);

//Reset input value
ctrl['quantity-'+index]=0;
}else{
ctrl.errorMessage = "Your cart already contains " + product.name + " by " + product.brand +
". Please visit your cart to adjust item quantities.";
}
};


//Products
ctrl.productList = [
{
name: 'Tissue (50 Ct)',
brand: 'Kleenex',
price: '5.00'
}, {
name: 'Whole Wheat Bread',
brand: 'Roman Meal',
price: '3.27'
}, {
name: 'Chili Spiced Mango',
brand: "Trader Joe's",
price: '4.56'
}
];
}


Please let me know if I need to explain further. Cheers!

Answer

Create a Shopping Cart Item directive to wrap the product

If you implemented it using transclude it might look like this:

<div class="section-container product-container">
  <div class="section-header product" ng-repeat="product in products.productList">
      <shopping-cart-item product="product" on-product-selected="addCart">
        <div class="product-info">
          <div>{{ product.name }} - <span>{{ product.price | currency }}</span></div>
          <div><a href="">{{ product.brand }}</a></div>
        </div>
        <div class="product-selection">
          <label for="product-quantity-{{$index}}">Quantity: </label>
          //Initialize new dynamically named ng-model for each quantity input
          <input id="product-quantity-{{$index}}" type="number" ng-model="quantity">
          <a href="" class="product-cart-btn" ng-click="FireProductSelected(product, quantity)">+</a>
        </div>
      </shopping-cart-item>
  </div>
</div>

Otherwise it might look like this:

<div class="section-container product-container">
  <div class="section-header product" ng-repeat="product in products.productList">
      <shopping-cart-item product="product"></shopping-cart-item>
  </div>
</div>