Tyler
Tyler

Reputation: 1891

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!

Upvotes: 0

Views: 300

Answers (2)

42shadow42
42shadow42

Reputation: 405

Create a product selector 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">
        <div class="product-info">
          <div>{{ product.name }} - <span>{{ product.price | currency }}</span></div>
          <div><a href="">{{ product.brand }}</a></div>
        </div>
      <product-selector product="product" on-product-selected="addCart">
        <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>
      </product-selector>
  </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">
      <product-selector product="product" on-product-selected="addCart"></product-selector>
  </div>
</div>

Upvotes: 1

sourdoughdetzel
sourdoughdetzel

Reputation: 664

I don't know that this is a great practice, but since ng-repeats generate their own private scope, you can actually generate a scope variable within the input tag. If you want it to show as "0" by default you can use ng-init to set it to 0 (again, probably not the cleanest).

<input id="product-quantity-{{$index}}" type="number" ng-model="quantity" ng-init="quantity = 0;" />
<a href="" class="product-cart-btn" ng-click="products.addCart(product, quantity)">+</a>

That should do the trick, I'll let you decide whether it's less hacky ;)

Upvotes: 0

Related Questions