0

I'm building a form that should support entering attributes for multiple instances of the same product. To allow a user to create an arbitrary number of instances I'm using ng-repeat and building an additional version of the form when an "add version" button is clicked. For static inputs this works as expected as a new form is created and the entered values are not linked between instances. However, I'm also intending to support a dynamic list of individual attributes using ng-repeat and in my current implementation the addVersion() function is copying both the number of attributes and the values within.

I've read several questions on this topic and it's clear to me I should be using $index but I'm afraid I'm new enough to Angular that I can't totally get my head around how to do this.

EDIT: Here's a working example that should highlight the problem

For the purpose of clarity I'm hoping to be able to generate a response that looks like:

Product Name: A Car
Product Description: You can sit in it and also drive it
Version 1
Price: $500
Quantity: 3
Features: 1) Goes fast 2) is red
Version 2
Price: $600
Quantity: 4
Features: 1) Goes really fast 2) is blue 3) has windshield wipers

But instead I'm seeing the values in features cloned... which makes sense because I'm clearly pushing them to the same array I just don't know how to change that :)

Right now a simplified version of the code looks like this:

HTML:

<form>
    <input type="text" ng-model="name" placeholder="Product Name">
    <textarea ng-model="description" placeholder="Product Description"></textarea>
    <button ng-click="addVersion()">Add Version</button>
    <!-- Additional feature inputs should be exclusive to each instance and not replicated across all -->
    <div ng-repeat="version in versions">
        <input type="number" ng-model="instance.price" placeholder="Price">
        <input type="number" ng-model="instance.quantity" placeholder="Quantity"> 
        <button ng-click="addInput()">Add Feature</button>
        <fieldset ng-repeat="feature in features">
            <input type="text" ng-model="instance.feature.name" placeholder="feature">
        </fieldset>
    </div>
</form>

JS:

$scope.versions = [{}];

$scope.addVersion = function() {
    $scope.versions.push({});
};


$scope.features = [];

$scope.addInput = function() {
    $scope.features.push({});
};

I think the solution here is stupidly obvious I'm just a bit lost. Thanks!

Community
  • 1
  • 1
isdn
  • 381
  • 1
  • 3
  • 15
  • I'm a bit lost after reading your question. What is `addVersion()` supposed to do..? Where does `product` come from..? `instance in instance` doesn't seem right. Where does that come from..? Please read [ask] , [How to create a Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve) – T J Dec 18 '15 at 07:40
  • Blahhhhh... tried to clean the code to make it easier to read here and looks like I may have made it harder. Fixing this now and will respond. Thanks! – isdn Dec 18 '15 at 07:44
  • Cleaned up with a Plunker at http://plnkr.co/edit/Vqy0ppKVsYxqATTc3ffm?p=preview. Hope it's readable now. – isdn Dec 18 '15 at 08:07

3 Answers3

0

I think you'll be able to fix the issue as shown below. Key change is that, youshould pass the version object to the addInput method and add feature to that instance.

I strongly suggest creating a directive for this task.

angular.module('Form', []).controller('multipleVersions', function($scope) {

  $scope.versions = [{
    features: [{
      features: [{}]
    }]
  }];

  $scope.addVersion = function() {
    $scope.versions.push({
      features: [{}]
    });
  };

  $scope.addInput = function(version) {
    version.features.push({});
  };

  $scope.removeInput = function(version, index) {
    version.features.splice(index, 1);
  };
});
input {
  display: block;
}
#addVersionButton {
  display: block;
}
.version {
  margin-top: 20px;
  border: 1px solid black;
}
.feature {
  display: inline;
}
.removeInput {
  display: inline;
}
fieldset {
  border: 0;
  padding: 0;
  margin: 0;
  min-width: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.20/angular.min.js"></script>
<form ng-app="Form" ng-controller="multipleVersions">
  <input type="text" ng-model="product.name" placeholder="Product Name">
  <textarea ng-model="product.description" placeholder="Product Description"></textarea>
  <button id="addVersionButton" ng-click="addVersion()">Add Version</button>
  <div class="version" ng-repeat="version in versions">
    <input type="number" ng-model="version.price" placeholder="Price">
    <input type="number" ng-model="version.quantity" placeholder="Quantity">
    <button ng-click="addInput(version)">Add Feature</button>
    <fieldset ng-repeat="feature in version.features">
      <input class="feature" type="text" ng-model="feature.name" placeholder="feature">
      <button class="removeInput" ng-click="removeInput(version,$index)">-</button>
    </fieldset>
  </div>
</form>
T J
  • 42,762
  • 13
  • 83
  • 138
  • This is so close! But I'm afraid the text entered into the "features" area is being copied across all of the input fields and removeInput() no longer works. Experimenting on my own but any help would be appreciated. Thanks for what you've done already though. – isdn Dec 18 '15 at 17:17
  • @brokyo see updated answer, btw it is better to add the add and remove functions as methods of version rather than `$scope` – T J Dec 19 '15 at 09:08
0

I think you're just overrating the variable instance...

When you working with ng-repeat, Its recommendable to you work with the repeater variable...

Can you try this?

<form>
    <input type="text" ng-model="name" placeholder="Product Name">
    <textarea ng-model="description" placeholder="Product Description"></textarea>
    <button ng-click="addVersion()">Add Version</button>
    <!-- Additional feature inputs should be exclusive to each instance and not replicated across all -->
    <div ng-repeat="version in versions">
        <input type="number" ng-model="version.instance.price" placeholder="Price">
        <input type="number" ng-model="version.instance.quantity" placeholder="Quantity"> 
        <button ng-click="addInput()">Add Feature</button>
        <fieldset ng-repeat="feature in features">
            <input type="text" ng-model="version.instance.feature.name" placeholder="feature">
        </fieldset>
    </div>
</form>

In repeat of features, I guess its wrong. I suggest you to add features to version.instance to it before.

Tip: If you testing it with chrome browser, press f12 and see the 'Console' log. If you accessing some property of null variable (my guess) you'll get an NullException error.

Gabriel
  • 83
  • 2
  • 9
-1

Thanks to everyone who helped. None of the solutions suggested covered everything however they definitely pushed me to the answer. Separate questions and solutions broken out below and a working version of the original http://plnkr.co/edit/T74bfNXxpYs16ENNUEjv?p=preview:

Duplication Of Dynamically Added Inputs: This was handled by passing version to the addInput() method and modifying the js to properly had the arrays:

  $scope.versions = [{
    features: [{
      features: [{}]
    }]
  }];

  $scope.addVersion = function() {
    $scope.versions.push({
      features: [{}]
    });
  };

  $scope.addInput = function(version) {
    version.features.push({});
  };

Thanks to T J for the solution in one of the answers below. This allowed individual inputs to be created but the text was cloned across all of them and the delete button no longer worked.

Cloned Text: Every group of inputs was writing to the same model at version.feature.name. Following the solution here and tracking by $index solved that.

Remove Buttons: The removeInput() method was no longer referencing the specific input it was paired with and required that I pass in both the $parent.$index (to reference the version) and the $index (to reference the specific input.)

Final code below:

HTML:

    <form>
      <input type="text" ng-model="name" placeholder="Product Name">
      <textarea ng-model="description" placeholder="Product Description"></textarea>
      <button id="addVersionButton" ng-click="addVersion()">Add Version</button>
    <div class="version" ng-repeat="version in versions">
      <span>Instance {{$index}}</span>
        <input type="number" ng-model="version.price" placeholder="Price">
        <input type="number" ng-model="version.quantity" placeholder="Quantity"> 
        <button ng-click="addInput(version)">Add Feature</button>
        <fieldset ng-repeat="feature in version.features">
            <!-- Additional feature inputs should be exclusive to each instance and not replicated across all -->
            <input class="feature" type="text" ng-model="version.features[$index].value" placeholder="feature">
            <button class="removeInput" ng-click="removeInput($parent.$index, $index)">-</button>
        </fieldset>
    </div>
</form>

JS

  $scope.versions = [{}];

  $scope.addVersion = function() {
      $scope.versions.push({});
  };


  $scope.features = [];

  $scope.addInput = function() {
      $scope.features.push({});
  };

    $scope.removeInput = function(index) {   
    $scope.features.splice(index, 1);
  };
Community
  • 1
  • 1
isdn
  • 381
  • 1
  • 3
  • 15