I'm building a form in Angular2 which contains a field that is an array of objects. I've so far built the table with a Delete Row button per row and an Add Row button. These use the JavaScript push() and slice() methods.
There is a big bug though:
When adding a new row, the content of the previous rows is deleted.
That is to say, the content of the row is deleted, not the row itself.
Any ideas why?
Component Code:
public addRow(): void {
this.table.push({});
}
public deleteRow(row: object): void {
this.table.splice(this.table.indexOf(row), 1);
}
HTML Template
<form #TimesheetForm="ngForm" (ngSubmit)="saveTimesheet()">
<div class="row">
<div class="col text-right">
<button type="button" class="btn" (click)="addRow()"><i class="fa fa-plus-square" aria-hidden="true"></i> Add Row</button>
</div>
</div>
<table class="table">
<thead>
<td class="table__header">Date</td>
<td class="table__header">Time</td>
<td class="table__header">Actions</td>
</thead>
<tbody>
<tr *ngFor="let row of table">
<td class="table__item">
<input class="input" [(ngModel)]="row.date" name="date">
</td>
<td class="table__item">
<input class="input" [(ngModel)]="row.time" name="time">
</td>
<td class="table__item">
<button type="button" class="btn btn--negative" (click)="deleteRow(row)"><i class="fa fa-times" aria-hidden="true"></i> Delete</button>
</td>
</tr>
<tr *ngIf="school.rows.length == 0">
<td colspan="3">No rows exist yet. Click Add Row to start logging your timesheet.</td>
</tr>
</tbody>
</table>
</div>
<div class="row">
<div class="col">
<button class="btn btn--positive" type="submit"><i aria-hidden="true" class="fa fa-check"></i> Save</button>
</div>
<div class="col text-right">
<button class="btn btn--negative"><i aria-hidden="true" class="fa fa-times"></i> Cancel</button>
</div>
</div>
</form>
With template driven forms, we need to remember that the name attribute needs to be unique, otherwise the fields will be evaluated as the same field. So what your form does now, is not adding new form controls when you add new rows, instead it manipulates the one and same form field. If you were to put something like <pre>{{TimesheetForm.value | json}}</pre> in your template, you can see, that despite pushing new rows, there is only one form control name date and one form control named time.
So what we need to do is to provide an unique name, we can do that by using the index of the items in your table array. So do the following:
<tr *ngFor="let row of table; let i = index">
<td>
<input [(ngModel)]="row.date" name="date{{i}}">
</td>
<td>
<input [(ngModel)]="row.time" name="time{{i}}">
</td>
<!-- more code here -->
</tr>
Related
I have created a textbox that has a "+" (Add button) and a "-" (delete button) . And when i click on + , i get one more text-box . And when i click on - , i delete that textbox with its value. To capture the value of the text-box , i use ngModel. I tried it without using a Form. Here the issues i am facing are -
[1.]
When i click on '+' button, i get the textbox successfully and i get its value also. But for subsequent additions when done using "+' button, i get the same value . And when i change its value, the value changes on every other text box done using " + " button.
[2.] I don't know how to delete the value of the text-box from the array-variable.
Please help
code -
app.component.html
<div *ngIf="addContainer">
<p style="margin-left: 200px; font-size:18px">Please enter the API
Object -</p>
<table align="center">
<tbody>
<tr >
<td >
<input type="text" placeholder="Enter a Node" [(ngModel)]= "firstValue">
</td>
<td >
<button type="button" style="margin-left: 10px" (click)="addOne(firstValue)" class="btn btn-success"> + </button>
</td>
<td>
<button type="button" style="margin-left: 10px" (click)="deleteOneMore()" class="btn btn-danger"> - </button>
</td>
</tr>
<tr *ngFor="let container of containers; let i = index;" #myElement>
<ng-container >
<td id="1myElement">
<input *ngIf="addMore" type="text" placeholder="Enter a Node" [(ngModel)]= "addedValue">
</td>
<td id="1myElement">
<button type="button" style="margin-left: 10px" (click)="addOneMore($event)" class="btn btn-success"> + </button>
</td>
<td>
<button type="button" style="margin-left: 10px" (click)="deleteOneMore()" class="btn btn-danger"> - </button>
</td>
</ng-container>
</tr>
<tr>
<td style="text-align:center">
<button type="button" (click)="showGraphs(firstValue,addedValue)" class="btn btn-dark">Search</button>
</td>
</tr>
</tbody>
</table>
</div>
app.component.ts
addOne(firstDropdValue) {
console.log("inside addOne = firstDropdValue = ",
firstDropdValue);
this.addMore = true;
this.containers.push(this.containers.length);
}
addOneMore(addedValue)
{
this.moreValues.push(addedValue);
console.log("Inside AddOneMore More Values = ",
this.moreValues);
}
deleteOneMore(){
this.containers.splice(this.index, 1);
}
showGraphs(firstV, addedV) {
console.log("inside showGraphs()");
console.log("firstValue =", firstV, "addedValue = ", addedV);
this.showEwayBill = true;
this.showCollection = true;
this.EwayBill();
this.Collection();
}
Image -
You should maintain an array for the newly added values like below and your add and remove should look like below
values = ["sometext"];
addOneMore() {
this.values.push("sivakumar");
}
deleteOneMore(index) {
this.values.splice(index, 1);
}
In the template file you can use that values or containers array to loop like below
<table>
<tr>
<td>
<input type="" [(ngModel)]='values[0]'>
</td>
<td>
<button type="button" style="margin-left: 10px" (click)="addOneMore(firstValue)" class="btn btn-success"> + </button>
</td>
<td>
<button type="button" style="margin-left: 10px" (click)="deleteOneMore(i)" class="btn btn-danger"> - </button>
</td>
</tr>
<ng-container *ngFor="let value of values; let i = index;">
<tr *ngIf="i > 0">
<td id="1myElement">
<input type="text" placeholder="Enter a Node" [(ngModel)]="values[i]">
</td>
<td id="1myElement">
<button type="button" style="margin-left: 10px" (click)="addOneMore()" class="btn btn-success"> + </button>
</td>
<td>
<button type="button" style="margin-left: 10px" (click)="deleteOneMore(i)" class="btn btn-danger"> - </button>
</td>
</tr>
</ng-container>
</table>
But, in this solution, still there is one issue that, in the textbox, we are allowing to enter one word at a time, I am not able to figure it out the reason, if anyone can, welcoming you to edit the answer ;-)
Working stackblitz is here
I use angularJS and I have one table on my page, I made one button to add row with input fields. My solution work good for adding rows and value of input fields in form, but doesn't work well where I need to delete particular row. I use one function for form, and another for adding and deleting rows.
It's like this:
<div ng-init="tmplCtr.newPeople()">
<div class="col-lg-12">
<input name="postsubmit" class="btn btn-default btn-sm" type="submit" value="Save table" ng-click="tmplCtr.newTable(tmplCtr.table)" />
<button class="btn btn-default btn-sm" ng-click="tmplCtr.editRow()">Add row</button>
</div>
<form name="peopleForm" novalidate>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th class="place-name">Name</th>
<th class="place-value">Lastname</th>
<th class="place-options">Options</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="people in tmplCtr.peoples">
<td class="place-name"><input type="text" name="" class="form-control" autocomplete="off" ng-model="tmplCtr.peoples.values[$index].name"></td>
<td class="place-last"><input type="text" name="" class="form-control" autocomplete="off" ng-model="tmplCtr.peoples.values[$index].lastname"></td>
<td class="place-options"><button class="btn btn-danger btn-md btn-delete" ng-click="tmplCtr.editRow($index)">Delete</button></td>
</tr>
</tbody>
</table>
</div>
</form>
When I press button add row I get row, but when I press delete row I delete row from screen but last row in array, not by index. Beside that also value of that row is not deleted from form scope. Function is:
function editRow(item) {
if(item == undefined) {
vm.peoples.push({});
} else {
vm.peoples.splice(item,1);
}
}
Can anyone help me?
I think its better to make two functions, the first one is for adding items, and the second one is for deleting like below:
function addEmptyItem(){
vm.peoples.push({});
}
function deleteItem(index){
vm.peoples.splice(index, 1);
}
Then in your view you can change tmplCtr.editRow($index) --> tmplCtr.deleteItem($index)
Furthermore it is strongly advised to use this syntax person.name etc in your repeat directive.
EDIT:
This is not tested by the way...
I need help with dynamically generated models inside ng-repeat. I have a HTML with ng-repeating table rows. Each row has an option to update image name and price with update button.
HTML
<tr role="row" class="odd" ng-repeat="i in slideShowImages track by $index">
<td>
<img ng-src="{{i.thumb_path}}" width="100px" height="auto" >
</td>
<td>
<div class="form-group">
<input type="text" name="imageName" ng-model="i.imageName[$index]"
class="form-control" id="imageName" required>
</div>
</td>
<td>{{i.date_taken | myDate}}</td>
<td>
<div class="form-group">
<input type="text" name="imagePrice" ng-model="i.imagePrice[$index]"
class="form-control" id="imagePrice" required>
</div>
</td>
<td>
{{i.position}}
</td>
<td>
<a ng-click="updateImage(i.id_thumb, $index)">
<button class="btn btn-block btn-success">Update</button>
</a>
</td>
<td>
<a ng-click="deleteImage(i.id_thumb, $index)">
<button class="btn btn-block btn-danger">Delete</button>
</a>
</td>
</tr>
And this is my controller function which is trying to get the values
$scope.updateImage = function(id, $index){
$scope.models = {};
console.log(id);
console.log($index);
console.log($scope.i.imageName[$index]);
console.log($scope.i.imagePrice[$index]);
};
// result of console logs
4
0
angular.js:13550 TypeError: Cannot read property 'imageName' of undefined
at Scope.$scope.updateImage (locationsCtrl.js:243)
at fn (eval at compile (angular.js:14432), <anonymous>:4:486)
at expensiveCheckFn (angular.js:15485)
at callback (angular.js:25018)
at Scope.$eval (angular.js:17229)
at Scope.$apply (angular.js:17329)
at HTMLAnchorElement.<anonymous> (angular.js:25023)
at HTMLAnchorElement.dispatch (jQuery-2.1.4.min.js:3)
at HTMLAnchorElement.r.handle (jQuery-2.1.4.min.js:3)
Guess i'm doing something wrong, based on error that imageName and imagePrice can't be undefined. I hope you guys can help me. If you need any additional information's please let me know and i will provide. Thank you in advance
You had ng-repeat with i in slideShowImages which says that, on each iteration i will have current element of collection over the UI only(not in controller scope). So you can not get $scope.i value inside controller. You have to pass that value from updateImage as a parameter like ng-click="updateImage(i)". Then you can play with i object which is available on UI.
HTML
<td>
<a ng-click="updateImage(i)">
<button class="btn btn-block btn-success">Update</button>
</a>
</td>
<td>
<a ng-click="deleteImage(i, $index)">
<button class="btn btn-block btn-danger">Delete</button>
</a>
</td>
Controller
$scope.updateImage = function(i){
console.log(i.imageName);
console.log(i.imagePrice);
};
You cannot ge the i object of ng-repeat in the controller.
What else one can do is filter using any other key in the json.
Example:
$scope.currentImageName = $filter('filter')(slideShowImages,{ id: $index});
Here assuming slideShowImages has a field id of value - $index.
Also, add the dependency injection$filter in the controller definition.
I am facing an issue with my Angular code, while populating the dropdown inside a HTML table (Code given below).
Could you please help me with what should be done inside modify() to populate the dropdown ?
HTML Code:
<table class="table" ng-app="empApp" ng-controller="employeeController">
<thead>
<tr class="info">
<th>Emp Name</th>
<th>Status</th>
<th colspan="2"> </th>
</tr>
</thead>
<tbody>
<tr ng-repeat="emp in employees">
<td>
<div ng-hide="editingData[emp.id]">{{ emp.name }}</div>
<div ng-show="editingData[emp.id]"><input type="text" ng-model="emp.name" /></div>
</div>
</td>
<td>
<div ng-hide="editingData[emp.id]">{{ emp.status.name }}</div>
<select ng-show="editingData[emp.id]" class="form-control" ng-model="emp.status"
ng-options="status.id as status.name for status in statuses"></select>
</td>
<td colspan="2">
<div class="btn-group">
<button type="submit" class="btn btn-primary" ng-hide="editingData[employee.id]" ng-click="modify(emp)">Modify</button>
<button type="submit" class="btn btn-primary" ng-show="editingData[employee.id]" ng-click="update(emp)">Update</button>
</div>
</td>
</tr>
</tbody>
</table>
Javascript Code:
var empApp = angular.module("empApp", []);
empApp.controller('employeeController', function($scope, $http) {
$scope.statuses = [{"id":1,"name":"Active"}, {"id":1,"name":"Inactive"}];
$scope.employees = [{"id":1,"name":"Mark","status":{"id":1,"name":"Active"}},{"id":2,"name":"Sara","status":{"id":2,"name":"Inactive"}}];
$scope.editingData = {};
for (var i = 0, length = $scope.employees.length; i < length; i++) {
$scope.editingData[$scope.employees[i].id] = false;
}
$scope.modify = function(employee){
$scope.editingData[employee.id] = true;
//Set Employee Status correctly here to populate the dropdown
};
});
My problem is I am NOT able to populate the dropdown with the existing value, as show in the diagram below:
I made this plunker, check if it's what you need.
1: Both status are with id 1. Change them
From:
$scope.statuses = [{"id":1,"name":"Active"}, {"id":1,"name":"Inactive"}];
To:
$scope.statuses = [{"id":1,"name":"Active"}, {"id":2,"name":"Inactive"}];
2:Change your select ng-model to emp.status.id.
From:
<select ng-show="editingData[emp.id]" class="form-control" ng-model="emp.status"
ng-options="status.id as status.name for status in statuses"></select>
To:
<select ng-show="editingData[emp.id]" class="form-control" ng-model="emp.status.id"
ng-options="status.id as status.name for status in statuses"></select>
3: Change your buttons ng-hide/ng-show
From:
<div class="btn-group">
<button type="submit" class="btn btn-primary" ng-hide="editingData[employee.id]" ng-click="modify(emp)">Modify</button>
<button type="submit" class="btn btn-primary" ng-show="editingData[employee.id]" ng-click="update(emp)">Update</button>
</div>
To:
<div class="btn-group">
<button type="submit" class="btn btn-primary" ng-hide="editingData[emp.id]" ng-click="modify(emp)">Modify</button>
<button type="submit" class="btn btn-primary" ng-show="editingData[emp.id]" ng-click="update(emp)">Update</button>
</div>
Update:
As #Rajesh said below you can store the status_id instead of entire status object. Check his fiddle.
Just replace your select tag part with this code
<select ng-show="editingData[emp.id]" class="form-control" ng-model="emp.status"
ng-options="status as status.name for status in statuses track by status.id"></select>
This is because in ng-model you are passing the full object and you need the full object to be tracked by ID in order to fill the dropdown and relevant dropdown value to be selected
Also in your buttons:
<div class="btn-group">
<button type="submit" class="btn btn-primary" ng-hide="editingData[emp.id]" ng-click="modify(emp)">Modify</button>
<button type="submit" class="btn btn-primary" ng-show="editingData[emp.id]" ng-click="update(emp)">Update</button>
</div>
Because you are referencing it by 'emp' and not 'employee' inside ng-repeat as it will be dynamic
You are looping through
<tr ng-repeat="emp in employees">
But in the select list, you are toggling based on:
editingData[employee.id]
Try changing that to
editingData[emp.id]
use emp.id in your button and also in your modify function and loop
use $scope.employees[i].isVisible = false and ng-hide = $scope.employees.isVisible ng-show = $scope.employees.isVisible
I'm developing an AngularJS application, using v1.3.9. I have a couple ng-if directives which are always evaluating true, and I can't figure out why.
<tr data-ng-repeat="line in chat">
<td class="message-timestamp">{{line.time | date:'short'}}</td>
<div ng-if="line.type === 'gist'">
<td class="message-nick">{{line.nick}}</td>
<td class="message-text">{{line.message}}
<p>
<code ng-show="showGist" walrus-auto-gistify data-gist-id="{{line.options[0]}}"></code>
<br>
<button ng-click="showGist = !showGist" class="btn btn-primary btn-xs">Show/Hide</button>
</p>
</td>
</div>
<div ng-if="false">
<td class="message-nick"><span class="glyphicon glyphicon-arrow-right"></span></td>
<td class="message-text">{{line.message}}</td>
</div>
</tr>
When I run this, both ng-if directives are evaluated as true, so both divs are displayed.
chat is just an array of objects.
I'm pretty sure it has to do with the fact that you have a <div> as a direct child of a <tr>. I've had issues with this type of design in the past where it causes strange behavior.
A solution to this is to put the <div>'s within the <td>, like below:
<tr data-ng-repeat="line in chat">
<td class="message-timestamp">{{line.time | date:'short'}}</td>
<td class="message-nick">
<div ng-if="line.type === 'gist'">{{line.nick}}</div>
<div ng-if="line.type !== 'gist'"><span class="glyphicon glyphicon-arrow-right"></span></div>
</td>
<td class="message-text">
<div ng-if="line.type === 'gist'">
<p>
<code ng-show="showGist" walrus-auto-gistify data-gist-id="{{line.options[0]}}"></code>
<br />
<button ng-click="showGist = !showGist" class="btn btn-primary btn-xs">Show/Hide</button>
</p>
</div>
<div ng-if="line.type !== 'gist'">{line.message}}</div>
</td>
</tr>
EDIT: Attempt at a JSFiddle here