I always get confused when trying to remove/delete nested objects in angularjs. I'm hoping someone can help me with a sample project I'm working on and determine what I'm doing incorrectly.
I have a UI in which there are several rows of nested data within a table. The user will click "Delete" in any row to initiate removing that given row. Then (and this is where I'm struggling) they would click "Remove Deleted Items" to remove all nested objects that have the "deleted" attribute set to true.
I've created an example so showcase what I'm trying to accomplish....
http://plnkr.co/edit/FrKIuEobEBNQ4Kl7TPaC?p=preview
Here's my AngularJS Code:
var myApp = angular.module("fruit", ['ngRoute', 'ngSanitize', 'ui.router']);
myApp.controller("FruitCtrl", function($scope) {
$scope.foods = [{
'id': 1, 'fruits': [{
'id': '1',
'name': 'Apple',
'color': 'Red',
'shape': 'Weird',
'size': 'medium',
'deleted': false
}, {
'id': '2',
'name': 'Orange',
'color': 'Orange',
'shape': 'Sphere',
'size': 'medium',
'deleted': false
}, {
'id': '3',
'name': 'Lime',
'color': 'Green',
'shape': 'Sphere',
'size': 'small',
'deleted': false
}, {
'id': '4',
'name': 'Lemon',
'color': 'Yellow',
'shape': 'Sphere',
'size': 'medium',
'deleted': false
}, {
'id': '5',
'name': 'Banana',
'color': 'Yellow',
'shape': 'Oblong',
'size': 'large',
'deleted': false
}]
}];
$scope.removeDeletedFruit = function(itemId, index) {
for (var i = 0; i < $scope.foods.length; i++) {
if ($scope.foods[i].id === itemId) {
$scope.foods[i].deleted.splice(index, 1);
break;
}
}
}
});
Here's my HTML:
<div class="container">
<h2>Total Fruits: {{foods[0].fruits.length}}</h2>
<button class="btn btn-danger" ng-click="removeDeletedFruit(fruit.id, $index)">Remove Deleted Items</button>
<table class="table" ng-repeat="food in foods">
<thead>
<tr>
<th>Fruit</th>
<th>Shape</th>
<th>Color</th>
<th>Size</th>
<th> </th>
</tr>
</thead>
<tbody>
<tr ng-repeat="fruit in food.fruits" ng-class="{'delete-item': fruit.deleted, '': !fruit.deleted}">
<td>
<span>{{fruit.name}}</span>
</td>
<td>
<span>{{fruit.shape}}</span>
</td>
<td>
<span>{{fruit.color}}</span>
</td>
<td>
<span>{{fruit.size}}</span>
</td>
<td>
<button type="button" class="btn btn-danger" ng-click="fruit.deleted = !fruit.deleted" ng-disabled="fruit.deleted">
<span>Delete</span>
</button>
<button type="button" class="btn btn-neutral" ng-click="fruit.deleted = !fruit.deleted" ng-if="fruit.deleted" ng-disabled="!fruit.deleted">
<i class="fa fa-history fa-1-5x"></i>
<span>Undo</span>
</button>
</td>
</tr>
</tbody>
</table>
</div>
Can someone assist with guiding me in the right direction or what's the best way to make this work?
I changed your function to use the Array.prototype.filter()
$scope.removeDeletedFruit = function(itemId, index) {
angular.forEach($scope.foods, function(fruit){
fruit.fruits = fruit.fruits.filter(function(a){return a.deleted == false})
});
}
demo here
Related
I have an array of objects which is shown below which I called $scope.parlist. I already did a research so that I can filter my nested ng-repeat whenever the user search for a particular account but I failed
[
{
bch: "001",
loan_product: [
{
id_code: "ML1",
asofmonitoring:[{ //asofmonitoring is fixed to have just one object here
days07:[
{
loan_no: "ML-XXX-XXX-XXX",
name: "John Papa"
},
{
loan_no: "ML-XXX-XXX-XXX",
name: "Grace Papa"
}
...
],
days08:[
{
loan_no: "ML-XXX-XXX-XXX",
name: "Earl Papa"
},
{
loan_no: "ML-XXX-XXX-XXX",
name: "Britney Papa"
}
...
]
...
}]
},
...
]
}
...
]
html
<tbody data-ng-repeat="par in parlist" ng-init="outerindex = $index">
<tr>
<td colspan="15" style="background-color:rgb(233, 236, 239)">
<table class="table table-sm">
<tbody ng-repeat="prod in par.loan_product" ng-init="innerindex = $index">
<tr>
<table>
<tbody ng-if="prod.asofmonitoring[0].days07.length > 0">
<tr>
<td colspan="2" class="text-left table-warning">
<input type="text" ng-model="q7[innerindex]" class="form-control" placeholder="Search account" aria-label="Search account" aria-describedby="basic-addon2">
</td>
</tr>
</tbody>
<tbody ng-repeat="days07 in prod.asofmonitoring[0].days07 | filter:q7[innerindex]">
<tr>
<td class="text-center" ng-bind="days07.loan_no">loading...</td>
</tr>
</tbody>
</table>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
My problem is I can't make the search filter to work in my ng-repeat="days07 in prod.asofmonitoring[0].days07 ng-repeat. I already make the other suggestion like putting a ng-init="outerindex = $index" and other solution but my ng-repeat wont filter. Can anybody help me out with this problem?
You've got a scope-related binding issue here.
Both ng-if and ng-repeat implicitly create new scopes.
You've not included any controller code but I think I'm making a fair assumption that you've not explicitly defining q7. Consequently, when q7 appears inside the ng-if, only this scope will be able to access the bound model. The ng-repeat is on a sibling element and thus doesn't have the same visibility, which is why nothing happens when you change the text filter model.
Quick solution here would be to explicitly initialise q7 in your controller to ensure no variable shadowing occurs.
Included a stripped down example below for you:
Edit: Updated to reflect commentary.
angular
.module('app', [])
.controller('ctrl', function ($scope) {
// Explicitly declare text filter model so `ng-repeat` does not create shadowed copies
$scope.q = {};
$scope.parlist = [
{
bch: '001',
loan_product: [
{
id_code: 'ML1',
asofmonitoring: [
{
days07: [
{
loan_no: 'ML-XXX-XXX-XXX',
name: 'John Papa',
},
{
loan_no: 'ML-XXX-XXX-XXX',
name: 'Grace Papa',
},
],
days08: [
{
loan_no: 'ML-XXX-XXX-XXX',
name: 'Earl Papa',
},
{
loan_no: 'ML-XXX-XXX-XXX',
name: 'Britney Papa',
},
],
},
],
},
],
},
{
bch: '002',
loan_product: [
{
id_code: 'ML1',
asofmonitoring: [
{
days07: [
{
loan_no: 'ML-XXX-XXX-XXX',
name: 'John Papa',
},
{
loan_no: 'ML-XXX-XXX-XXX',
name: 'Grace Papa',
},
],
days08: [
{
loan_no: 'ML-XXX-XXX-XXX',
name: 'Earl Papa',
},
{
loan_no: 'ML-XXX-XXX-XXX',
name: 'Britney Papa',
},
],
},
],
},
],
},
];
});
<div ng-app="app" ng-controller="ctrl">
<div ng-repeat="par in parlist">
<div ng-repeat="prod in par.loan_product">
<div ng-if="prod.asofmonitoring[0].days07.length">
<input type="text" ng-model="q[par.bch][$index]" placeholder="bch: {{par.bch}}">
</div>
<div ng-repeat="days07 in prod.asofmonitoring[0].days07 | filter:q[par.bch][$index]">
<div ng-bind="days07.loan_no">loading...</div>
</div>
</div>
</div>
<pre>q = {{ q | json }}</pre>
</div>
<script src="https://unpkg.com/angular#1.7.5/angular.min.js"></script>
I have a table of data which is three columns. The first two columns should be sorted alphabetically but the third column (value) needs a custom sort: high, medium, low or low, medium, high when reversed.
I have the alphabetical sorting working so far, but having trouble with the custom sort of the 3rd column (value).
JS
(function(angular) {
'use strict';
angular.module('orderByExample2', [])
.controller('ExampleController', ['$scope', function($scope) {
var items = [
{name: 'name1', type: 'Web', value: 'high'},
{name: 'name2', type: 'Email', value: 'medium'},
{name: 'name3', type: 'DNS', value: 'medium'},
{name: 'name4', type: 'Web', value: 'high'},
{name: 'name5', type: 'Web', value: 'medium'},
{name: 'name6', type: 'Email', value: 'high'},
{name: 'name7', type: 'Web', value: 'low'},
{name: 'name8', type: 'FTP', value: 'low'},
{name: 'name9', type: 'Web', value: 'high'}
];
$scope.propertyName = 'value';
$scope.reverse = false;
$scope.items = items;
$scope.sortBy = function(propertyName) {
$scope.reverse = ($scope.propertyName === propertyName) ?
!$scope.reverse : false;
$scope.propertyName = propertyName;
};
}]);
})(window.angular);
HTML
<body ng-app="orderByExample2">
<div ng-controller="ExampleController">
<pre>Sort by = {{propertyName}}; reverse = {{reverse}}</pre>
<hr/>
<button ng-click="propertyName = null; reverse = false">Set to default</button>
<hr/>
<table class="items">
<tr>
<th>
<button ng-click="sortBy('name')">Name</button>
<span class="sortorder" ng-show="propertyName === 'name'" ng-class="{reverse: reverse}"></span>
</th>
<th>
<button ng-click="sortBy('type')">Type</button>
<span class="sortorder" ng-show="propertyName === 'type'" ng-class="{reverse: reverse}"></span>
</th>
<th>
<button ng-click="sortBy('value')">Value</button>
<span class="sortorder" ng-show="propertyName === 'value'" ng-class="{reverse: reverse}"></span>
</th>
</tr>
<tr ng-repeat="item in items | orderBy:propertyName:reverse">
<td>{{item.name}}</td>
<td>{{item.type}}</td>
<td>{{item.value}}</td>
</tr>
</table>
</div>
</body>
Plunker: https://plnkr.co/edit/zBKd3ot22NGZd0y5XTow?p=preview
As #jb-nizet says, you will need a custom function here that would return the value to order each element by. This is a way to implement that function:
function sortingValue(e) {
return ['high', 'medium', 'low'].indexOf(e.value);
}
I've made a fork of your Plunkr.
I am trying to create dynamic rows based on button click. The rows have drop down boxes. The issue is when I add new row and select an option in the new row the options which I selected in previous rows are also changing, I mean the previous rows options are not binding properly.
My HTML code :
<div id="features" ng-controller="featuresCtrl">
<br>
<div class="row">
<div class="form-group col-md-6">
<table cellpadding="15" cellspacing="10">
<thead>
<tr>
<th style="text-align: center;">Feature</th>
<th style="text-align: center;">Type</th>
<th style="text-align: center;">Value</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr></tr>
<tr ng-repeat="r in rows">
<td>
<select ng-model="r.data.model" ng-options="option.name for option in data.availableOptions"
ng-change="getValues(r.data.model.name)">
<option value="">Select Feature</option>
</select>
</td>
<td>
<select ng-model="r.updateData.model" ng-options="option.name for option in updateData.availableOptions"
ng-change="getBinSet(r.updateData.model.name)">
<option value="">Select Type</option>
</select>
</td>
<td>
<select ng-model="r.binData.model" ng-options="option.name for option in binData.availableOptions">
<option value="">Select Value</option>
</select>
</td>
<td ng-if="showAdd">
<div class="text-center">
<button ng-click="addRow()">Add</button>
</div>
</td>
<td ng-if="showAdd">
<div class="text-center">
<button ng-click="remove($index)">Remove</button>
</div>
</td>
</tr>
<tr>
<td></td>
<td style="align-self: center;">
<button style="align-self: center" ng-click="submitForm(rows)">Submit</button>
</td>
<td></td>
</tr>
</tbody>
</table>
<br>
</div>
My angular JS code :
'use strict';
var testControllers = angular.module('testControllers');
testControllers.controller('featuresCtrl', ['$scope','$route','$filter', '$http', '$location','$window','$timeout', 'NgTableParams',
function($scope, $route, $filter, $http, $location, $window, $timeout, NgTableParams) {
$scope.rows = [{}];
$scope.nrows = [];
$scope.showAdd = false;
$scope.addRow = function() {
$scope.rows.push({
});
};
$scope.remove = function(index) {
$scope.rows.splice(index, 1);
};
$scope.submitForm = function(rows) {
console.log("rows", rows);
};
$scope.data = {
model: null,
availableOptions: [
{ name: 'TestA'},
{ name: 'TestB'},
{ name: 'TestC'},
{ name: 'TestD'}
]
};
$scope.getValues = function (featureType) {
console.log("getValues", featureType);
$scope.showAdd = false;
if (featureType != null) {
$http.get('/getPropensityValues.do', {params: {'featureType': featureType}}).success(function (data) {
var test = [];
angular.forEach(data, function (item) {
test.push({name: item});
});
$scope.updateData = {
model: null,
availableOptions : test
};
});
}
};
$scope.getBinSet = function (featureType) {
console.log("getBinSet", featureType);
$scope.showAdd = true;
if (featureType != null) {
$scope.binData = {
model: null,
availableOptions: [
{name: '1'},
{name: '2'},
{name: '3'},
{name: '4'},
{name: '5'},
{name: '6_10'},
{name: '10_15'},
{name: '16_20'},
{name: '21_25'},
{name: '26_30'},
{name: '31_35'},
{name: '36_40'},
{name: '41_45'},
{name: '46_50'},
{name: '>50'}
]
};
}
};
}]);
Can some one tell me what I am doing wrong here ? I tried with another example - https://plnkr.co/edit/Jw5RkU3mLuGBpqKsq7RH?p=preview
Even here I am facing same issue when selecting the 2nd row 1st row also changing. Is it wrong to use r.data.model to bind each row's values ?
I updated your plunker to work:
https://plnkr.co/edit/4sJHHaJPEuYiaHjdnFBp?p=preview
You weren't defining what the model was when you were redefining the options menus (side note: you should leverage underscore.js's difference function within a filter to create the appropriate display options dynamically)
Anyway, in your addRow() function, I changed it from this:
if(val=== 'TestB') {
$scope.data1 = {
model: null,
availableOptions: [
{ name: 'TestA'},
{ name: 'TestC'},
{ name: 'TestD'}
]
};
}
to this:
if(val=== 'TestB') {
$scope.data1 = {
model: 'TestB',
availableOptions: [
{ name: 'TestA'},
{ name: 'TestC'},
{ name: 'TestD'}
]
};
}
Let me know if this helps
UPDATE:
I recreated the functionality from scratch because I think you were overthinking things. All that is needed here is very simple ng-repeat, ng-options, and ng-model bindings.
<tr ng-repeat="r in rows track by $index">
<td>
<select ng-model="r.name"
ng-options="option.name as option.name for option
in availableOptions">
<option value="">Select Value</option>
</select>
</td>
<td>
<input type="button" ng-click="addRow()" value="Add">
</td>
<td>
<input type="button" ng-click="deleteRow($index)"
value="Delete">
</td>
</tr>
and for the angular
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.rows = [{name:""}];
$scope.addRow = function(){
$scope.rows.push({name:""});
}
$scope.availableOptions = [
{ name: 'TestA'},
{ name: 'TestB'},
{ name: 'TestC'},
{ name: 'TestD'}
];
$scope.deleteRow = function(index){
$scope.rows.splice(index,1);
}
});
I didn't throw in the difference thing yet, but it should be easy.
var app = angular.module('app', ["angucomplete-alt"]);
app.controller('MainController', function($scope) {
$scope.countries = [
{name: 'Hosting 1', code: '100.00'},
{name: 'Hosting 2', code: '200.00'},
{name: 'Hosting 3', code: '300.00'},
{name: 'Hosting 4', code: '400.00'},
{name: 'Hosting 5', code: '500.00'},
{name: 'Hosting 6', code: '600.00'},
];
$scope.countrySelected = function(selected) {
//$scope.invoice.item.lol = 1;
window.alert('You have selected ' + selected.title);
};
$scope.taxesData = {
singleSelect: null,
availableOptions: [
{id: '6.50', name: '6,5%'},
{id: '16.00', name: '16%'},
{id: '23.00', name: '23%'}
],
selectedOption: {id: '23.00', name: '23%'}
};
$scope.invoice = {
items: [{
description: '',
}]
};
$scope.addItem = function() {
$scope.invoice.items.push({
description: '',
price: 10.99,
});
};
$scope.removeItem = function(index) {
$scope.invoice.items.splice(index, 1);
};
$scope.total = function() {
var total = 0;
angular.forEach($scope.invoice.items, function(item) {
total += item.qty * item.price;
})
return total;
};
$scope.taxtotal = function() {
var taxtotal = 0;
angular.forEach($scope.invoice.items, function(item) {
taxtotal += item.qty * item.price * item.taxesData.repeatSelect * 0.01;
})
return taxtotal;
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<!--ng-controller="MainController"-->
<div ng-app="app"><div ng-controller="MainController">
<table class="table table-condensed table-striped">
<tr>
<th>Product Name</th>
<th>Item Price</th>
<th>Qty</th>
<th>Tax Rate</th>
<th>Line Total</th>
<th>Line Taxes Total</th>
<th></th>
</tr>
<tr ng:repeat="item in invoice.items">
<td angucomplete-alt id="ex1" placeholder="Search countries" maxlength="50" pause="100" selected-object="selectedCountry" local-data="countries" search-fields="name" title-field="name" minlength="1" input-class="form-control form-control-small" match-class="highlight"></td>
<td><input type="text" value="{{selectedCountry.originalObject.code}}"></td>
<td><input type="text" ng:model="item.qty" ng:required class="input-mini"></td>
<!--<td><input type="text" ng:model="item.taxRate"class="input-small"></td>-->
<td>
<select name="repeatSelect" ng-model="item.taxesData.repeatSelect">
<option ng-repeat="option in taxesData.availableOptions" value="{{option.id}}">{{option.name}}</option>
</select>
</td>
<td>{{item.qty * item.mama | currency}}</td>
<td>{{item.qty * itme.mama * item.taxesData.repeatSelect * 0.01 | currency}}</td>
<td>
[<a href ng:click="removeItem($index)">X</a>]
</td>
</tr>
<tr>
<td><a href ng:click="addItem()" class="btn btn-small">add item</a></td>
<td></td>
<td>Totals:</td>
<td></td>
<td>{{total() | currency}}</td>
<td>{{taxtotal() | currency}}</td>
</tr>
</table>
</div>
</div></div>
As you can see in this jfiddle I am using angular with angucomplete-alt plugin to manage an order form.
I have a scope "countries" which feed the autocomplete box.
Now i am trying to ,
when choosing a country (product :P) i want the field Item Price to feeded with the correct value (e.g. if i select hosting 1, the Item price must completed with 100.00 value.
The problem is that :
1) If I set ng:model to this field (because I want to use the price for the totals) the field does not completed.
2) If I set just value="{{selectedCountry.originalObject.code}}, the field completed but I haven't a ng:object to use it for the totals.
It should be working if you set ng:model as the following, for your Price input field:
<input type="text" ng:model="selectedCountry.originalObject.code">
See working fiddle
I am not able to show proper icon on sorting of column, if I click of one column for sorting then icons of all other columns also gets updated.
Only sorted column should get update on sorting of respective column.
I have listed my code here : http://plnkr.co/edit/UonOgNz0s16iKJkWLwyg?p=preview
HTML Code :
<table border="1">
<thead>
<tr>
<th ng-repeat="key in getKeysOfCollection(colors[0])" ng-click="ctrl.predicate = key; ctrl.reverse=!ctrl.reverse;">
{{key}}
<span class="fa fa-sort columnSortIcons" ng-if="ctrl.reverse"></span>
<span class="fa fa-sort-down columnSortIcons" ng-if="!(ctrl.reverse)"></span>
<span class="fa fa-sort-up columnSortIcons" ng-if="ctrl.reverse"></span>
</th>
</tr>
<tr>
<th ng-repeat="key in getKeysOfCollection(colors[0])">
<input type="text" ng-model="search[key]" />
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in colors | filter:search | orderBy:ctrl.predicate:ctrl.reverse">
<td ng-repeat="key in getKeysOfCollection(item)">{{item[key]}}</td>
</tr>
</tbody>
</table>
JS Code :
this.predicate='id';
this.reverse=false;
$scope.search = {};
$scope.colors = [{
'id': 1,
'productId': 1001,
'productName': 'prd 1',
'minimumLevel': 2,
'price': 12.50,
'productDate': '2014-11-01T06:41:30.809Z'
}, {
'id': 2,
'productId': 1002,
'productName': 'prd 2',
'minimumLevel': 23,
'price': 12.54,
'productDate': '2014-11-02T06:41:30.809Z'
}, {
'id': 3,
'productId': 1003,
'productName': 'prd 3',
'minimumLevel': 2,
'price': 12.50,
'productDate': '2014-11-04T06:41:30.809Z'
}, {
'id': 4,
'productId': 1004,
'productName': 'prd 4',
'minimumLevel': 2,
'price': 12.50,
'productDate': '2014-11-22T06:41:30.809Z'
}, {
'id': 5,
'productId': 1005,
'productName': 'prd 5',
'minimumLevel': 2,
'price': 12.50,
'productDate': '2014-11-18T06:41:30.809Z'
}];
$scope.getKeysOfCollection = function(obj) {
obj = angular.copy(obj);
if (!obj) {
return [];
}
return Object.keys(obj);
}
In your ng-if statements you aren't checking to see if that column is the sorted column (the predicate):
<span class="fa fa-sort-down columnSortIcons" ng-if="!(ctrl.reverse) && key == ctrl.predicate"></span>
<span class="fa fa-sort-up columnSortIcons" ng-if="ctrl.reverse && key == ctrl.predicate"></span>
Updated code.