I'm trying to make a simple to do list app using Angular. When the user clicks the checkbox, the list item should disappear. Right now, the list item disappears upon clicking the checkbox, but the checkbox of the list item directly below it also gets checked (although the list item doesn't disappear).
<ul ng-repeat="item in arr track by $index">
<li>
<input type="checkbox" ng-click="check(item)">
<span>{{ item }}</span>
</li>
</ul>
$scope.arr = [
'todo 1',
'todo 2',
'todo 3'
];
$scope.check = function(item) {
var indexOf = $scope.arr.indexOf(item);
if (indexOf !== -1) {
$scope.arr.splice(indexOf, 1);
}
};
Is there any way to solve this?
Not sure how this is working as-is, but the normal way to accomplish this is with an ng-model on the <input>. Make your items into objects so they can store more complex state, which also lets you drop track by.
<ul ng-repeat="item in arr">
<li>
<input type="checkbox" ng-model="item.done">
<span>{{item.label}}</span>
</li>
</ul>
$scope.arr = [
{label: 'todo 1', done: false},
{label: 'todo 2', done: false},
{label: 'todo 3', done: false}
];
That should get your checking and unchecking set up correctly. To drop items when they're done, I recommend a filer on the ng-repeat.
ng-repeat="item in arr | filter: {done: 'false'}"
https://plnkr.co/edit/ybikvwysC6ZEd7CeZnnt?p=preview
The checkbox directly under the deleted item was automatically being checked because the information, that first the element in array is checked was still valid, but the length of the array reduces with every splice method executed. That's why you have to actually tell angular that check another element, which takes index: 0 in the array after previous element gets spliced is unnecessary.
Here's the solution: Plunker link
Raw code:
<ul ng-repeat="item in arr track by $index">
<li>
<input type="checkbox" ng-click="check(item)" ng-model="item.selected">
<span>{{ item }}</span>
</li>
</ul>
$scope.arr = [
'todo 1',
'todo 2',
'todo 3'
];
$scope.check = function(item) {
var indexOf = $scope.arr.indexOf(item);
if (indexOf !== -1) {
$scope.arr.splice(indexOf, 1);
$scope.item.selected = false;
}
That happens because you use "track by $index". Try without track by, or use track by with some unique field (id), in which case your list should consist of objects, not primitives. For example:
$scope.arr = [
{id: 1, name: 'todo 1'},
{id: 2, name: 'todo 2'},
{id: 3, name: 'todo 3'} //...
];
<ul ng-repeat="item in arr track by item.id">
<li>
<input type="checkbox" ng-click="check($index)">
<span>{{item.name}}</span>
</li>
</ul>
$scope.check = function(index) {
$scope.arr.splice(index, 1);
}
You need to prevent the click event from performing the default action
<input type="checkbox" ng-click="check(item);$event.preventDefault();" />
Angular is removing the element, on the click, but then the (moved) element also gets the click event, checking it. $event.preventDefault() will prevent this.
Here is a working plunk
Related
I have a list of options and then I call an API which validates those options. I plan to display whether an option is valid or not.
I start with this array
$scope.preValidationArray = [
{ id: 1, description: 'Item 1' },
{ id: 2, description: 'Item 2' },
{ id: 3, description: 'Item 3' },
];
and get the following response
$scope.validations = [
{ id: 1, valid: true },
{ id: 2, valid: false },
{ id: 3, valid: false },
];
how can I use ng-repeat to match the right id and NOT rebuild the array again. I would rather just update the valid property on the original array whenever I get a new set of validations. I'm thinking of something like <div ng-bind="validations.valid on option.id">
<ul id="options">
<li ng-repeat="option in preValidationArray track by option.id"
ng-class="{'invalid': !option.valid}">
<div ng-bind="option.description"></div>
//////////////// Here I want to do something like
Valid: <div ng-bind="validations.valid on option.id">
</li>
</ul>
I am going to have a Revalidate button which is going to return the set of validations again so ideally I don't want to rebuild this array over and over again.
Bare in mind I have to trigger a class on <li> element using ng-class if it's invalid.
One simple solution would be using another ng-repeat for the validations:
<ul id="options">
<li ng-repeat="option in preValidationArray track by option.id">
<div ng-bind="option.description"></div>
Valid: <div ng-repeat="validation in validations | filter:{id:option.id} track by validation.id">{{validation.valid}}</div>
</li>
</ul>
Check demo: DEMO
Are your preValidationArray and validations arrays 1:1? Meaning, if pre-validation has 15 elements, does validations as well (and does validations[0] correspond to preValidationArray[0] and so on)? If so you could track on the index and be safe in this instance. With that caveat in mind, you could try something like this:
<ul id="options">
<ng-repeat="option in preValidationArray track by $index">
<ng-bind="option.description"></div>
Valid: <div ng-bind="validations[$index].valid">
</li>
</ul>
Create a function:
$scope.validById = function(id) {
return $scope.validations.find(_ => _.id == id).valid;
};
And use it:
<ul id="options">
<li ng-repeat="option in preValidationArray track by option.id"
ng-class="{'invalid': !validById(option.id)}">
<div ng-bind="option.description"></div>
//////////////// Here I want to do something like
̶V̶a̶l̶i̶d̶:̶ ̶<̶d̶i̶v̶ ̶n̶g̶-̶b̶i̶n̶d̶=̶"̶v̶a̶l̶i̶d̶a̶t̶i̶o̶n̶s̶.̶v̶a̶l̶i̶d̶ ̶o̶n̶ ̶o̶p̶t̶i̶o̶n̶.̶i̶d̶"̶>̶
Valid: <div ng-bind="validById(option.id)"></div>
</li>
</ul>
I'm trying to ignore a property called title in my angular filter. I have a dataset like the below example:
const data = [
{
title: 'Title 1'
groups: [
{...},
{...},
{...}
]
},
{
title: 'Title 2'
groups: [
{...},
{...},
{...}
]
},
{
title: 'Title 3'
groups: [
{...},
{...},
{...}
]
}
];
And i'm using the ng-repeat with filter to iterate over the objects, and other loop to iterate over the groups:
<input ng-model="search">
<div ng-repeat="item in data | filter:search">
<h1>{{item.title}}</h1>
<ul>
<li ng-repeat="group in item.group | filter:search">
<span>{{group.something}}</span>
</li>
</ul>
</div>
Is working fine, but now i would like to ignore the title in the search. I did try several things, like: filter:search:item.title (in the first ng-repeat), or remove the first filter:search, but all tries failed. What i'm missing? Do i need a custom search or something like that?
Thank you.
You can specifically enter properties you want to filter and leave out title:
<li ng-repeat="group in item.groups | filter: { something: search }">
The above code will only filter based on the something property.
More answers and explanations here: AngularJS filter only on certain objects
If you type and no filtering the title property, just remove the first filter. This way when you type the li's isnt match will hide, but their h1's will stay the same place.
You should create custom filter, where you can specify which property should be excluded(ignore parameter) from consideration:
angular.module('app', []).controller('MyController', ['$scope', function($scope) {
$scope.data = [
{name:"Tom", title:'London'},
{name:"Max", title:'Moscow'},
{name:"Henry", title:'NY'},
{name:"Paul", title:'NY'},
{name:"Sam", title:'Paris'}
];
}]).filter('myfilter',function(){
return function(input, search, ignore){
if(!search)
return input;
var result = [];
for(var item of input)
for(var prop in item)
if(prop != ignore && item[prop].indexOf(search) != -1)
{
result.push(item) ;
break;
}
return result;
}
});
<script src="//code.angularjs.org/snapshot/angular.min.js"></script>
<body ng-app="app">
<div ng-controller="MyController">
search: <input type='text' ng-model='search' ng-init='search="a"'/>
ignore: <input type='text' ng-model='ignore' ng-init='ignore="title"'/>
<ul>
<li ng-repeat='item in data | myfilter: search: ignore'>
{{item.name}} {{item.title}}
</li>
</ul>
</div>
</body>
On my page I have angular ui accordion, inside of each panel, I'm rendering list with items and checkboxes, also I have checkbox "select all".
For selection method and logic I used this resource. In this resource logic seems working, but however I'm putting this logic to my code, it stops working.
What I want to achieve is when all checkboxes are selected, checkbox "select all" has been selected automatically, and if some of checkboxes is unselect, checkbox "select all" has to be unselect as well.
I have tried multiple suggestions provided here, here, here, but in the end I'm getting the same result.
I appreciate if somebody could help me to resolve my problem.
$scope.categories = [
{
id: 1,
name: "category 1"
},
{
id: 2,
name: "category 2"
},
{
id: 3,
name: "category 3"
}
]
$scope.selectedAll = false;
$scope.selectAll = function(array) {
$scope.selectedAll = !$scope.selectedAll;
angular.forEach(array, function(item) {
item.Selected = $scope.selectedAll;
});
};
$scope.checkIfAllSelected = function(array) {
$scope.selectedAll = array.every(function(item) {
return item.Selected == true
})
};
html
<div>
<div class="row" ng-class="{'selected': selectedAll, 'default': !selectedAll}">
<div>Select all
<input type="checkbox"
ng-model="selectedAll" ng-click="selectAll(categories)" >
</div>
</div>
<div class="row" ng-repeat="item in categories | orderBy : 'id'" ng-class="{'selected': item.selected, 'default': !item.selected}">
<div > {{ item.name }}
<input type="checkbox"
ng-model="item.Selected" ng-click="checkIfAllSelected(categories)"
>
</div>
</div>
This is my plunker
Please take a look at this fork of your plunker: https://plnkr.co/edit/OW3F1VMke9iLuNkt5p3o?p=preview
Two things:
1. It's a good practice to create an object to your view model (you can find it under the name model in the plunker $scope.model. This will solve 2 way data binding issues.
2. I have changed the ng-click to ng-change (this is not part of the solution though - its just more correct in my opinion).
Please let me know if you need more clarifications.
I trying to add sub menu by clicking the button but it doesn't work. My data looks like:
$scope.menuItems = [
{ name: 'Menu1',
children: [
{ name: 'Sub1' },
{ name: 'Sub2'} ]
},
{ name: 'Menu1',
children: [
{ name: 'Sub1' } ]
}
];
$scope.addSubItem = function() {
$scope.menuItems.children.push({
name: 'Test Sub Item'
});
};
http://plnkr.co/edit/2R5kpY2iGhiE6FEy65Ji?p=preview
Plunker Solution here
You need to modify the submenu button markup to pass the reference to the menu item that the button resides in:
<ul class="sub-menu">
<li ng-repeat="menuItem in menuItem.children" ng-include="'sub-tree-renderer.html'"></li>
<button class="btn btn-warning" style="margin-top: 10px;" ng-click="addSubItem(menuItem)">Add Sub Menu Item</button>
</ul>
and then in your addSubItem function operate directly on the item like this:
$scope.addSubItem = function(item) {
item.children.push({
name: 'Sub' + (item.children.length + 1)
});
};
Also make sure that every time you create new item the children array is defined as empty array instead of being undefined:
$scope.addItem = function() {
$scope.menuItems.push({
name: 'Test Menu Item',
children: []
});
};
I would recommend using data value object that you can construct a new item with instead of using hand typed object literals as if you use them in many places it is easy to make mistake and cause bugs which happens only in some places and are time consuming to find.
You need to specify the index of the menuItems array that you wish to add the sub menu to.
This would add a sub menu to the first menu item:
$scope.menuItems[0].children.push({
name: 'Test Sub Item'
});
Also, if this is of n depth and can vary depending on the data that is driving the menu, you could build a controller for the menu item and have it recursively add a child/show in your template based on the node you are clicking on. Then you don't need to explicitly worry about indexes.
firstly you should determine sub menu by it index. here you can use $index for this. when you add new Item just add item name. when you need add children array also.
<ul class="sub-menu">
<li ng-repeat="menuItem in menuItem.children" ng-include="'sub-tree-renderer.html'"></li>
<button class="btn btn-warning" style="margin-top: 10px;" ng-click="addSubItem($index)">Add Sub Menu Item</button>
</ul>
and in controller
$scope.addSubItem = function(index) {
$scope.menuItems[index].children.push({
name: 'Test Sub Item'
});
};
$scope.addItem = function() {
var item = {
name: 'Test Menu Item',
children: []
};
$scope.menuItems.push(item);
};
I try to create multiple select elements on a AngularJS page in a single context. names is a array of available name objects and cores is the array with available cores. For each name, I would like to have a drop down with the available cores. This works fine, but I'm not able to pre-select the currently selected core. That one is stored in name.core.
<ul>
<li ng-repeat='name in names'>
{{ name.name }} - {{ name._url }} <span ng-click='delete_name(name)'>[x]</span>
<select ng-model='?'
ng-options='core.id as core.instance for core in cores'
ng-change='coreChanged(name, ?)'>
<option value=''>No core assigned</option>
</select>
</li>
</ul>
I'm quite new to AngularJS, so if another structure or additional controllers, scopes, ... would be more appropriate, I'm open for suggestions.
Update:
ng-model='name.core' as proposed by haki does not work, probably because name.core is not an instance from the cores array. I tried ng-model='name.core.id' but that results in every drop down having the same (wrong) selection.
This works fine for me:
<ul>
<li ng-repeat='name in names'>
{{ name.name }} - {{ name._url }} <span ng-click='delete_name(name)'>[x]</span>
<select ng-model='name.core'
ng-options='core.id as core.instance for core in cores'
ng-change='coreChanged(name)'>
<option value=''>No core assigned</option>
</select>
</li>
</ul>
Double-check on how your view corresponds to your model. My scope variables look like this:
.controller('MyController', function($scope){
$scope.names = [
{ name: 'Marc', url: 'http://www.example.com/one', core: "1" },
{ name: 'Achim', url: 'http://www.example.com/two', core: "2" },
{ name: 'Jenny', url: 'http://www.example.com/three', core: "3" }
];
$scope.cores = [
{ id: '1', instance: 'First' },
{ id: '2', instance: 'Second' },
{ id: '3', instance: 'Third' }
];
$scope.coreChanged = function(name){
console.log(name);
}
});
Working Plunker