How can i init my select with option, that selects on backend (Symfony). Now i init my select (ng-model="myselect" example) like ng-init="myselect='0'"
I need to set actual selected option in ng-init, but if i remove ng-init directive it creates empty option with value ? undefined:undefined ?
What should i do?
<div ng-app="myapp">
<fieldset ng-controller="FirstCtrl">
<select
ng-options="p.id as p.first + ' ' + p.last for p in people"
ng-model="selectedPerson"></select>
{{ selectedPerson }}
</fieldset>
var myapp = angular.module('myapp', []);
myapp.controller('FirstCtrl', function ($scope) {
$scope.selectedPerson = 2;
$scope.people = [
{ id: 1, first: 'John', last: 'Rambo', actor: 'Silvester' },
{ id: 2, first: 'Rocky', last: 'Balboa', actor: 'Silvester' },
{ id: 3, first: 'John', last: 'Kimble', actor: 'Arnold' },
{ id: 4, first: 'Ben', last: 'Richards', actor: 'Arnold' }
];
});
This is what you expected..
Also check with this link http://jsfiddle.net/kaehejgo/
I was able to win this problem.
Anonymous function that turn vanilla html selects into angular models using my own function that converts string into object reference.
(function(){
window.selectControl = [];
var selects = document.querySelectorAll(".filter__select_range");
selects.forEach( function(elem, index) {
var _elem = angular.element(elem);
if (elem.querySelector("[selected]") !== undefined) {
window.selectControl[_elem.attr("data-model")] = elem.querySelector("[selected]").value;
_elem.attr({
"ng-model": _elem.attr("data-model"),
"ng-change": _elem.attr("data-change"),
"ng-init": _elem.attr("data-model") + "='"+ _elem.find('option:selected').val() +"'",
});
_elem.removeAttr('data-model').removeAttr('data-change');
stringToPath($scope, _elem.attr('ng-model'), elem.querySelector("[selected]").value);
}
});
})();
And stringToPath function
function stringToPath(obj, str, val) {
str = str.split(".");
for (var i = 0; i < str.length; i++) {
if (i != str.length-1 && str[i] != '') {
if (typeof obj[str[i]] !== "object") {
obj[str[i]] = {}
}
stringToPath(obj[str[i]], str.splice(1).join("."), val);
}
else {
obj[str[i]] = val;
}
}
return obj;
}
And one of my select in twig syntax
{{- form_widget(form, {'attr': attr|merge({'class': 'filter__select filter__select_range', 'data-model': 'FilterCollection.range.area.min', 'data-change': "checkSelectedFilters();"})}) -}}
Related
I am trying to search through a deeply nested array and find if a key exists anywhere inside. I have written up a piece of code which does the traversal, but because it is not recursive (only self calling) it cannot return whether or not it has found anything. It just returns undefined since it reaches the end of the function on one of passes.
I was wondering if there was a way I could do this which would allow me to return true on the first occurrence of a specific key.
Here is a JS bin of what I have been working with so far:
https://jsbin.com/qaxuwajuso/edit?js,console
And here is a direct paste of the code from the above example:
function traverse(item, key) {
if (typeof item === 'object' && !Array.isArray(item) && item !== null) {
// Object
for (let itemKey in item) {
if (itemKey === key) {
// Is it possible to return true and break out of the function here?
console.log('found the key: ' + itemKey + ' With value: ' + item[itemKey]);
}
traverse(item[itemKey], key);
}
} else if (Array.isArray(item)) {
// Array
for (let i = 0; i < item.length; ++i) {
traverse(item[i], key);
}
}
}
Any help would be greatly appreciated. Thank you for your time!
Sure you just need to return a flag of some kind to trigger the loops to stop
/*
* I am trying to search the following json array for any occurance of the key "statePath".
* In a perfect world I would be able to find the first occurance, and return true from the
* function.
*
* The following data is not real, I was just trying to write as much nested stuff as possible
* to test that it traverses as far as needed.
*/
const data = [
{
id: '2144d998-4c33-4b03-93d2-f6c675b24508',
element: 'div',
props: {
className: 'testing',
name: [
{
first: 'John',
last: {
statePath: 'lastName',
anArray: [
{
anObject: {
anotherArray: [
{
doesItWork: {
statePath: 'hello',
},
},
],
},
},
],
},
},
{
first: 'Jane',
last: {
statePath: 'lastName',
},
},
],
},
children: 'hi',
},
];
function traverse(item, key) {
if (typeof item === 'object' && !Array.isArray(item) && item !== null) {
// Object
for (let itemKey in item) {
if (itemKey === key) {
console.log('found the key: ' + itemKey + ' With value: ' + item[itemKey]);
// not sure what you want the end "return" of the func to be, I'm returning the value. You could return true here instead, you could return a reference to the parent object, lots of possibilities
return item[itemKey];
}
var found = traverse(item[itemKey], key);
if (found !== undefined) return found;
// otherwise keep looking
}
} else if (Array.isArray(item)) {
// Array
for (let i = 0; i < item.length; ++i) {
var found = traverse(item[i], key);
if (found !== undefined) return found;
}
}
}
var value = traverse(data, 'statePath');
console.log("value is " + value);
You could use for...in and store result in one var and then check that var before you call function again and break loop if value is found.
const data = [{"id":"2144d998-4c33-4b03-93d2-f6c675b24508","statePath":"div","props":{"className":"testing","name":[{"first":"John","last":{"statePath":"lastName","anArray":[{"anObject":{"anotherArray":[{"doesItWork":{"statePath":"hello"}}]}}]}},{"first":"Jane","last":{"statePath":"lastName"}}]},"children":"hi"}]
function traverse(item, key) {
let result = false;
for (var i in item) {
if (i == key) {
result = true;
break;
}
if (typeof item[i] == 'object' && !result) {
result = traverse(item[i], key)
}
}
return result
}
console.log(traverse(data, 'statePath'))
My variant:
const data = [{id: '2144d998-4c33-4b03-93d2-f6c675b24508', element: 'div', props: {className: 'testing', name: [{first: 'John', last: {statePath3: 'lastName', anArray: [{anObject: {anotherArray: [{doesItWork: {statePath1: 'hello',},},],},},],},}, {first: 'Jane', last: {statePath: 'lastName',},},],}, children: 'hi',},];
function traverse(data, find) {
for (let k in data) {
let deepHaveKey = typeof data[k] === 'object' && traverse(data[k], find)
if (find === k || deepHaveKey)
return true
}
return false
}
console.log(traverse(data, 'statePath')); // true
console.log(traverse(data, 'state')); // false
Problem
I have a combo box, basically a select element that is filled with an array of complex objects by ng-options. When I update any object of the collection on second-level, this change is not applied to the combo box.
This is also documented on the AngularJS web site:
Note that $watchCollection does a shallow comparison of the properties of the object (or the items in the collection if the model is an array). This means that changing a property deeper than the first level inside the object/collection will not trigger a re-rendering.
Angular view
<div ng-app="testApp">
<div ng-controller="Ctrl">
<select ng-model="selectedOption"
ng-options="(selectedOption.id + ' - ' + selectedOption.name) for selectedOption in myCollection track by selectedOption.id">
</select>
<button ng-click="changeFirstLevel()">Change first level</button>
<button ng-click="changeSecondLevel()">Change second level</button>
<p>Collection: {{ myCollection }}</p>
<p>Selected: {{ selectedOption }}</p>
</div>
</div>
Angular controller
var testApp = angular.module('testApp', []);
testApp.controller('Ctrl', ['$scope', function ($scope) {
$scope.myCollection = [
{
id: '1',
name: 'name1',
nested: {
value: 'nested1'
}
}
];
$scope.changeFirstLevel = function() {
var newElem = {
id: '1',
name: 'newName1',
nested: {
value: 'newNested1'
}
};
$scope.myCollection[0] = newElem;
};
$scope.changeSecondLevel = function() {
var newElem = {
id: '1',
name: 'name1',
nested: {
value: 'newNested1'
}
};
$scope.myCollection[0] = newElem;
};
}]);
You can also run it live in this JSFiddle.
Question
I do understand that AngularJS does not watch complex objects within ng-options for performance reasons. But is there any workaround for this, i.e. can I manually trigger re-rendering? Some posts mention $timeout or $scope.apply as a solution, but I could utilize neither.
A quick hack I've used before is to put your select inside an ng-if, set the ng-if to false, and then set it back to true after a $timeout of 0. This will cause angular to rerender the control.
Alternatively, you might try rendering the options yourself using an ng-repeat. Not sure if that would work.
Yes, it's a bit ugly and needs an ugly work-around.
The $timeout solution works by giving AngularJS a change to recognise that the shallow properties have changed in the current digest cycle if you set that collection to [].
At the next opportunity, via the $timeout, you set it back to what it was and AngularJS recognises that the shallow properties have changed to something new and updates its ngOptions accordingly.
The other thing I added in the demo is to store the currently selected ID before updating the collection. It can then be used to re-select that option when the $timeout code restores the (updated) collection.
Demo: http://jsfiddle.net/4639yxpf/
var testApp = angular.module('testApp', []);
testApp.controller('Ctrl', ['$scope', '$timeout', function($scope, $timeout) {
$scope.myCollection = [{
id: '1',
name: 'name1',
nested: {
value: 'nested1'
}
}];
$scope.changeFirstLevel = function() {
var newElem = {
id: '1',
name: 'newName1',
nested: {
value: 'newNested1'
}
};
$scope.myCollection[0] = newElem;
};
$scope.changeSecondLevel = function() {
// Stores value for currently selected index.
var currentlySelected = -1;
// get the currently selected index - provided something is selected.
if ($scope.selectedOption) {
$scope.myCollection.some(function(obj, i) {
return obj.id === $scope.selectedOption.id ? currentlySelected = i : false;
});
}
var newElem = {
id: '1',
name: 'name1',
nested: {
value: 'newNested1'
}
};
$scope.myCollection[0] = newElem;
var temp = $scope.myCollection; // store reference to updated collection
$scope.myCollection = []; // change the collection in this digest cycle so ngOptions can detect the change
$timeout(function() {
$scope.myCollection = temp;
// re-select the old selection if it was present
if (currentlySelected !== -1) $scope.selectedOption = $scope.myCollection[currentlySelected];
}, 0);
};
}]);
Explanation of why changeFirstLevel works
You are using (selectedOption.id + ' - ' + selectedOption.name) expression to render select options labels. This means an {{selectedOption.id + ' - ' + selectedOption.name}} expression is working for select elements label. When you call changeFirstLevel func the name of selectedOption is changing from name1 to newName1. Because of that html is rerendering indirectly.
Solution 1
If the performance is not a problem for you you can simply delete the track by expression and the problem will be solved. But if you want performance and rerender at the same time both will be a bit low.
Solution 2
This directive is deep watching the changes and apply it to model.
var testApp = angular.module('testApp', []);
testApp.directive('collectionTracker', function(){
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
var oldCollection = [], newCollection = [], ngOptionCollection;
scope.$watch(
function(){ return ngModel.$modelValue },
function(newValue, oldValue){
if( newValue != oldValue )
{
for( var i = 0; i < ngOptionCollection.length; i++ )
{
//console.log(i,newValue,ngOptionCollection[i]);
if( angular.equals(ngOptionCollection[i] , newValue ) )
{
newCollection = scope[attrs.collectionTracker];
setCollectionModel(i);
ngModel.$setUntouched();
break;
}
}
}
}, true);
scope.$watch(attrs.collectionTracker, function( newValue, oldValue )
{
if( newValue != oldValue )
{
newCollection = newValue;
oldCollection = oldValue;
setCollectionModel();
}
}, true)
scope.$watch(attrs.collectionTracker, function( newValue, oldValue ){
if( newValue != oldValue || ngOptionCollection == undefined )
{
//console.log(newValue);
ngOptionCollection = angular.copy(newValue);
}
});
function setCollectionModel( index )
{
var oldIndex = -1;
if( index == undefined )
{
for( var i = 0; i < oldCollection.length; i++ )
{
if( angular.equals(oldCollection[i] , ngModel.$modelValue) )
{
oldIndex = i;
break;
}
}
}
else
oldIndex = index;
//console.log(oldIndex);
ngModel.$setViewValue(newCollection[oldIndex]);
}
}}
});
testApp.controller('Ctrl', ['$scope', function ($scope) {
$scope.myCollection = [
{
id: '1',
name: 'name1',
nested: {
value: 'nested1'
}
},
{
id: '2',
name: 'name2',
nested: {
value: 'nested2'
}
},
{
id: '3',
name: 'name3',
nested: {
value: 'nested3'
}
}
];
$scope.changeFirstLevel = function() {
var newElem = {
id: '1',
name: 'name1',
nested: {
value: 'newNested1'
}
};
$scope.myCollection[0] = newElem;
};
$scope.changeSecondLevel = function() {
var newElem = {
id: '1',
name: 'name1',
nested: {
value: 'newNested2'
}
};
$scope.myCollection[0] = newElem;
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.5/angular.min.js"></script>
<div ng-app="testApp">
<div ng-controller="Ctrl">
<p>Select item 1, then change first level. -> Change is applied.</p>
<p>Reload page.</p>
<p>Select item 1, then change second level. -> Change is not applied.</p>
<select ng-model="selectedOption"
collection-tracker="myCollection"
ng-options="(selectedOption.id + ' - ' + selectedOption.name) for selectedOption in myCollection track by selectedOption.id">
</select>
<button ng-click="changeFirstLevel()">Change first level</button>
<button ng-click="changeSecondLevel()">Change second level</button>
<p>Collection: {{ myCollection }}</p>
<p>Selected: {{ selectedOption }}</p>
</div>
</div>
Why don't you just simply track collection by that nested property ?
<select ng-model="selectedOption"
ng-options="(selectedOption.id + ' - ' + selectedOption.name) for selectedOption in myCollection track by selectedOption.nested.value">
Update
Since you don't know which property to track you can simply track all properties passing a function on track by expression.
ng-options="(selectedOption.id + ' - ' + selectedOption.name) for selectedOption in myCollection track by $scope.optionsTracker(selectedOption)"
And on Controller:
$scope.optionsTracker = (item) => {
if (!item) return;
const firstLevelProperties = Object.keys(item).filter(p => !(typeof item[p] === 'object'));
const secondLevelProperties = Object.keys(item).filter(p => (typeof item[p] === 'object'));
let propertiesToTrack = '';
//Similarilly you can cache any level property...
propertiesToTrack = firstLevelProperties.reduce((prev, curr) => {
return prev + item[curr];
}, '');
propertiesToTrack += secondLevelProperties.reduce((prev, curr) => {
const childrenProperties = Object.keys(item[curr]);
return prev + childrenProperties.reduce((p, c) => p + item[curr][c], '');
}, '')
return propertiesToTrack;
}
I think that any solution here will be either overkill (new directive) or a bit of a hack ($timeout).
The framework does not automatically do it for a reason, which we already know is performance. Telling angular to refresh would be generally frowned upon, imo.
So, for me, I think the least intrusive change would be to add a ng-change method and set it manually instead of relying on the ng-model change. You'll still need the ng-model there but it would be a dummy object from now on. Your collection would be assigned on the return (.then ) of the response , and let alone after that.
So, on controller:
$scope.change = function(obj) {
$scope.selectedOption = obj;
}
And each button click method assign to the object directly:
$scope.selectedOption = newElem;
instead of
$scope.myCollection[0] = newElem;
On view:
<select ng-model="obj"
ng-options="(selectedOption.id + ' - ' + selectedOption.name) for selectedOption in myCollection track by selectedOption.id"
ng-change="change(obj)">
</select>
Hope it helps.
I'm having trouble converting the following Lodash statement to something that works in an application that I inherited at work and am trying to fix bugs in. At the moment, we are having issues with only one device on our system returning when two devices have the same name and the following code seems to be the culprit as it would only return the first "true" value in an array:
var group = _.find(groupList, {id: id});
How would I successfully convert this to a statement that iterates over all objects in an array and then returns those objects? I've tried the following options to no avail:
var group = _.filter(groupList, {id: id});
and
var group = _.every(groupList, {id: id});
and
var group = _.forEach(groupList, {id: id})
return {id};
I know I am probably missing something in my syntax. Any help would be much appreciated. Running Lodash v3.7.0
Here's the rest of the code in the directive in case anyone is interested or sees something else I might be missing:
define(['./../_module'], function (directives) {
'use strict';
directives.directive('dmGroupedList', ['$compile', '$state', 'APP_CONSTANTS', function ($compile, $state, APP_CONSTANTS) {
return {
restrict: 'A',
scope: {
items: '=',
groupBy: '=',
actions: '=',
nonameGroupLabel: '='
},
templateUrl: function (elem, attrs) {
return attrs.templateUrl || 'views/shared-templates/grouped-list.html';
},
link: function (scope, element, attrs) {
scope.$watchGroup(['items', 'groupBy', 'nonameGroupLabel'], function () {
scope.groupList = [];
scope.groupedItems = {};
var actions = scope.actions[scope.groupBy];
_.forEach(scope.items, function (item) {
scope.handlers.getGroups(scope.groupList, item, scope.items, scope.groupBy, actions);
});
_.forEach(scope.groupList, function (group) {
var items = scope.groupedItems[group.id];
items = _.sortBy(items, function (item) {
return item.description;
});
scope.groupedItems[group.id] = items;
});
var groupsToSort = _.where(scope.groupList, {unassigned: false});
var unassignedGroups = _.sortBy(_.where(scope.groupList, {unassigned: true}), 'name');
scope.groupList = _.sortBy(groupsToSort, function (group) {
return group.name;
});
//adds unassigned groups to a new array via the javascript "push" method
if (angular.isDefined(unassignedGroups)) {
for (var i = 0; i < unassignedGroups.length; i++) {
scope.groupList.push(unassignedGroups[i]);
}
}
});
scope.handlers = {
getGroups: function (groupList, item, items, groupBy, actions) {
var group = item[groupBy];
if (_.isEmpty(group)) {
scope.handlers.addGroupToList(groupList, APP_CONSTANTS.DEVICE_NONE_NAME_MAPPING.NONE, items, groupBy, item, actions, scope);
}
else {
if (angular.isArray(group) || angular.isObject(group)) {
_.forEach(group, function (groupName) {
if (groupName == APP_CONSTANTS.ZERO) {
scope.handlers.addGroupToList(groupList, APP_CONSTANTS.DEVICE_NONE_NAME_MAPPING.NONE, items, groupBy, item, actions, scope);
return;
}
scope.handlers.addGroupToList(groupList, groupName, items, groupBy, item, actions, scope);
})
} else {
scope.handlers.addGroupToList(groupList, group, items, groupBy, item, actions, scope);
}
}
},
addGroupToList: function (groupList, groupId, items, groupBy, item, handlers, scope) {
var id = _.camelCase(groupId);
var group = _.find(groupList, {id: id});
//var group = _.forEach(groupList, {id: id})
//return {id};
if (!group) {
var name = '';
var unassigned = false;
var link = null;
if (groupId == APP_CONSTANTS.DEVICE_NONE_NAME_MAPPING.NONE || groupId == APP_CONSTANTS.DEVICE_NONE_NAME_MAPPING.NONE_PARENT_ID) {
if (groupId == APP_CONSTANTS.DEVICE_NONE_NAME_MAPPING.NONE_PARENT_ID) {
name = APP_CONSTANTS.DEVICE_NONE_NAME_MAPPING.NONE;
} else {
name = APP_CONSTANTS.DEVICE_NONE_NAME_MAPPING.UNASSIGNED;
}
unassigned = true;
} else {
link = handlers.getGroupLink(groupId);
name = handlers.getGroupName(groupId, items);
}
group = {id: id, name: name, unassigned: unassigned, link: link};
groupList.push(group);
}
scope.groupedItems[group.id] = scope.groupedItems[group.id] || [];
if (angular.isDefined(handlers.processingGroup)) {
handlers.processingGroup(group, groupList, groupId, items, groupBy, item, handlers, scope);
} else {
scope.groupedItems[group.id].push({
description: handlers.getItemDescription(item),
link: handlers.getItemLink(item)
})
}
}
};
}
};
}]);
});
You can just use filter:
var group = groupList.filter((group) => group.id === id);
EDIT: to return only the element, and not an array, when there is only one match, you can do the following:
var checkSingle = (groups) => groups.length === 1 ? groups[0] : groups;
var group = checkSingle(groupList.filter((group) => group.id === id));
You can _(groupList).groupBy('id').get(id):
var groupList = [
{ id: 1, name: 'site' },
{ id: 2, name: 'test' },
{ id: 2, name: 'prod' },
{ id: 3, name: 'dev' },
{ id: 4, name: 'back' },
{ id: 4, name: 'front' },
{ id: 5, name: 'sprint' }
];
console.log(_(groupList).groupBy('id').get(2));
console.log(_(groupList).groupBy('id').get(3));
console.log(_(groupList).groupBy('id').get(4));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
Im using $filter to filter my array.
var filteredData = params.filter() ?
$filter('filter')($scope.myNgTable.data, params.filter()):
$scope.myNgTable.data;
I created a multiple select filter, to filter by, but it returns an array:
col: ["a","b"], which does not work with $filter for what I see.
I want when the filter is col: ["a","b"] it would show all rows with col contains "a" or col contains "b". (if there is only a way for equal that s fine: col == "a" or col == "b")
Is that possible?
Answer:
.filter("in_Array", function ($filter){
return function(data, filter){
var out = [];
$.each(filter, function(key,val) {
var obj = {};
for(var i=0;i<val.length;i++) {
obj[key] = val[i];
var tmp = $filter('filter')(data, obj);
out = $.unique($.merge(out, tmp));
}
});
return out;
};
});
Look this example. This use the multi filter when click in checkbox
http://jsfiddle.net/rzgWr/123/
HTML:
<div ng-controller="MyCtrl">
<h4>Pick a brand to see the models</h4>
<div ng-init="group = (cars | groupBy:'make')" style="width:100%">
<div ng-repeat="m in group" style="width:100px; float:left;">
<b><input type="checkbox" ng-model="useMakes[$index]"/>{{m}}</b>
</div>
</div>
<div style="clear:both;"></div>
<div>
<ul>
<li ng-repeat="car in cars | filter:filterMakes()"><p><b>{{car.make}} - {{car.model}}</b></p></li>
</ul>
</div>
</div>
JS:
var myApp = angular.module('myApp',[]);
function MyCtrl($scope, filterFilter) {
$scope.useMakes = [];
$scope.filterMakes = function () {
return function (p) {
for (var i in $scope.useMakes) {
if (p.make == $scope.group[i] && $scope.useMakes[i]) {
return true;
}
}
};
};
$scope.cars = [
{model: '316', make: 'Bmw'},
{model: '520', make: 'Bmw'},
{model: 'Fiesta', make: 'Ford'},
{model: 'Focus', make: 'Ford'},
{model: 'Clio', make: 'Renault'},
{model: 'Toledo', make: 'Seat'},
{model: 'Leon', make: 'Seat'},
{model: 'Insignia', make: 'Opel'},
{model: 'Astra', make: 'Opel'},
{model: 'Corsa', make: 'Opel'}
];
}
var uniqueItems = function (data, key) {
var result = new Array();
for (var i = 0; i < data.length; i++) {
var value = data[i][key];
if (result.indexOf(value) == -1) {
result.push(value);
}
}
return result;
};
myApp.filter('groupBy',
function () {
return function (collection, key) {
if (collection === null) return;
return uniqueItems(collection, key);
};
});
I'm trying to achieve an array with items that displays as a title and image and when the user click the title the item should be added to a div. However, I canĀ“t make it to work as when I click on "Add me", there is only a blank item added, with no image and no title.
I got the delete-function to work but not the add-function.
Here is what I got:
The "Add me"-button and the list of items
<li ng-repeat="item in items.data">
{{item.title}} <img ng-src="{{ item.image }}" /><a ng-click="deleteItem($index)" class="delete-item">x</a>
<button ng-click="addItem()">Add Me</button>
</li>
The array
var myApp = angular.module("myApp", []);
myApp.factory("items", function () {
var items = {};
items.data = [{
title: "Item 1",
image: "img/item01.jpg"
}, {
title: "Item 2",
image: "img/item02.jpg"
}, {
title: "Item 3",
image: "img/item03.jpg"
}, {
title: "Item 4",
image: "img/item04.jpg"
}];
return items;
});
And the functions for add and delete
function ItemsController($scope, items) {
$scope.items = items;
$scope.deleteItem = function (index) {
items.data.splice(index, 1);
}
$scope.addItem = function () {
items.data.push({
title: $scope.items.title
});
}
}
You need to pass the item under iteration.
<button ng-click="addItem(item)">Add Me</button>
and add the title to the newly added:
$scope.addItem = function(item) {
items.data.push({
title: item.title
});
}
it is better not to use $index (it can cause issue when used with filters eg: orderBy filter) instead just pass the item to delete and:
$scope.deleteItem = function(item) {
items.data.splice(items.indexOf(item), 1);
}
You also have an invalid html, li must be a child of ul or ol.
A sample implementation:
function($scope, items) {
$scope.items = items;
$scope.cart = [];
$scope.deleteItem = function(item) {
var cart = $scope.cart;
//Get matched item from the cart
var match = getMatchedCartItem(item);
//if more than one match exists just reduce the count
if (match.count > 1) {
match.count -= 1;
return;
}
//Remove the item if current count is 1
cart.splice(cart.indexOf(item), 1);
}
$scope.addItem = function(item) {
//Get matched item from the cart
var match = getMatchedCartItem(item), itemToAdd ;
//if item exists just increase the count
if (match) {
match.count += 1;
return;
}
//Push the new item to the cart
itemToAdd = angular.copy(item);
itemToAdd.count = 1;
$scope.cart.push(itemToAdd);
}
function getMatchedCartItem(item) {
/*array.find - Find the shim for it in the demo*/
return $scope.cart.find(function(itm) {
return itm.id === item.id
});
}
Demo
angular.module('app', []).controller('ctrl', function($scope, items) {
$scope.items = items;
$scope.cart = [];
$scope.deleteItem = function(item) {
var cart = $scope.cart;
var match = getMatchedCartItem(item);
if (match.count > 1) {
match.count -= 1;
return;
}
cart.splice(cart.indexOf(item), 1);
}
$scope.addItem = function(item) {
var match = getMatchedCartItem(item);
if (match) {
match.count += 1;
return;
}
var itemToAdd = angular.copy(item);
itemToAdd.count = 1;
$scope.cart.push(itemToAdd);
}
function getMatchedCartItem(item) {
return $scope.cart.find(function(itm) {
return itm.id === item.id
});
}
}).factory("items", function() {
var items = {};
items.data = [{
id: 1,
title: "Item 1",
image: "img/item01.jpg"
}, {
id: 2,
title: "Item 2",
image: "img/item02.jpg"
}, {
id: 3,
title: "Item 3",
image: "img/item03.jpg"
}, {
id: 4,
title: "Item 4",
image: "img/item04.jpg"
}];
return items;
});
if (!Array.prototype.find) {
Array.prototype.find = function(predicate) {
if (this == null) {
throw new TypeError('Array.prototype.find called on null or undefined');
}
if (typeof predicate !== 'function') {
throw new TypeError('predicate must be a function');
}
var list = Object(this);
var length = list.length >>> 0;
var thisArg = arguments[1];
var value;
for (var i = 0; i < length; i++) {
value = list[i];
if (predicate.call(thisArg, value, i, list)) {
return value;
}
}
return undefined;
};
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<ul>
<li ng-repeat="item in items.data" id="item{{item.id}}">
{{item.title}}
<img ng-src="{{ item.image }}" />
<button ng-click="addItem(item)">Add Me</button>
</li>
</ul>
<p>Cart:</p>
<ul>
<li ng-repeat="item in cart">
{{item.title}} | Count: {{item.count}}
<a ng-click="deleteItem(item)" class="delete-item">X</a>
</li>
</ul>
</div>
I am using Array.prototype.find, you would need to add the shim (as mentioned in the demo) to be able to make it work for non supported browsers.