Using splice on dynamic array in Angular repeater - javascript

I have an issue when trying to delete an object from an array using splice. I have an array that is dynamically created through a UI and stored in a scope variable called $scope.productAttributes.Products. This is an example of what it looks like...
[
{
"ProductLabel":"Net",
"Code":"ela",
"Site":"SITE1"
},
{
"ProductLabel":"Link",
"Code":"eli",
"Site":"SITE1"
},
{
"ProductLabel":"24-port managed PoE switch",
"Code":"24p",
"Site":"SITE2"
},
{
"ProductLabel":"Dedicated Firewall",
"Code":"ded",
"Site":"SITE2"
},
{
"ProductLabel":"Link",
"Code":"eli",
"Site":"SITE3"
},
{
"ProductLabel":"IPv4 Addresses",
"Code":"ip4",
"Site":"SITE3"
}
]
I then display that array in an angular repeater and group it by 'site' (which might be part of the problem)...
<div ng-repeat="(key, value) in productAttributes.Products | groupBy: 'Site'">
<strong>{{key}}</strong>
<div ng-repeat="site in value">
<h4>{{site.ProductLabel}}</h4>
<sapn href="" ng-click="deleteItem($index)" class="text-danger">Remove {{site.ProductLabel}}</span>
</div>
</div>
</div>
On the delete button I pass in the index of the object and use the splice function...
$scope.deleteItem = function (index) {
$scope.productAttributes.Products.splice(index, 1);
};
So the issue is that the $index is always zero (I noticed this from a console.log) as I mentioned that I think it might be down to the groupBy but I am not sure. anyone know whats going wrong? Thanks
UPDATE:
It would seem the problem is with the $index in the nested repeater. So if the json above the structure would be...
SITE1:
Product: Net - $index: 0
Product: Link - $index: 1
SITE2:
Product: 24-port - $index: 0
Product: Dedicated - $index: 1
SITE3:
Product: Link - $index: 0
Product: IPV4 - $index: 1
So if I try to delete the IPV4 product in SITE3, it removes the LINK product in Site1 as it has the same $index. any ideas how I can fix that?

We can not rely on $index as it does not contain the updated value after you remove an item from array.
Pass the object dynamically from UI and delete it from model using below code:
In Html:
<div ng-repeat="(key, value) in productAttributes.Products | groupBy: 'Site'">
<strong>{{key}}</strong>
<div ng-repeat="site in value">
<h4>{{site.ProductLabel}}</h4>
<sapn href="" ng-click="deleteItem(site)" class="text-danger">Remove {{site.ProductLabel}}</span>
</div>
</div>
</div>
In JavaScript:
$scope.productAttributes.Products.splice
($scope.productAttributes.Products.indexOf(site), 1);
This causes model to update with updates values in repeater and re-renders it on UI.

OK - I ended up doing it this way and it seems to work
$scope.deleteItem = function (item) {
var index = $scope.productAttributes.Products.indexOf(item);
$scope.productAttributes.Products.splice(index, 1);
};
So passing in the whole object seems to have worked. I'm not sure why.

Related

How do I remove duplicate values ​in a v-for array?

I coded like this to remove duplicate values.
vue
<div class="col-md-6" style="float: left">
<ul class="list-group">
<li class="list-group-item"
:class="{ active: index1 == currentIndex1 }"
v-for="(member, index1) in uniqueMemName"
v-bind:value="member.mem_name"
:key="index1"
#click="setActiveMember(member, index1)"
>
<strong style="margin-bottom: 5px"> {{member.mem_name}} </strong>
</li>
</ul>
</div>
vue (script)
computed: {
uniqueMemName() {
return _.uniqBy(this.members, function(m) {
return m.mem_name;
});
}
},
I also installed lodash. But I get an error. Which part is wrong?
Please let me know if there is another method other than the one I wrote.
++) error console
console window
++) array information:
I have tables A and B. Table A imports only the mem_name column. Table B imports all columns.
Example ->
a.mem_name
b.col1
b.col2
mem1
10
20
mem1
30
40
mem2
50
60
I'm working on making duplicate mem_names into one at this time. Using lodash's unique features.
If you want to use lodash just for this, which sounds like the case, I suggest that there may be a better way without it, only using newer vanilla JS:
...
computed: {
uniqueMemName() {
return [...new Set(this.members.map(m => m.mem_name))]
}
}
Sets are always unique, so mapping and converting to a set and then back to array gives us a unique array.

Angular 1.5 filter with ng-repeat not working by track by id

so I tried so many different ways to get this done. Followed so many StackOverflow and could not get this to work. All I am trying to do is to filter some list items based on the value of a boolean property. Below is the picture of my object data. The closest example I am following is this question Filtering an Angular 1.2 ng-repeat with "track by" by a boolean property. Still not working. does it have anything to do with an object literal and this type of filtering with property only works with array? I am new to javascript so not sure. Also using angular material, virtual repeat container and other material based things are not affecting the result, I can display the whole data, just the filtered by this specific property not working
loadAssets = () => {
var self = this;
self.infiniteAssets = {
numLoaded_: 0,
toLoad_: 0,
items: [],
pageNum:1,
virtualIndex:0,
getItemAtIndex: function (index) {
this.virtualIndex=index;
if (index > this.numLoaded_) {
this.fetchMoreItems_(index);
return null;
}
return this.items[index];
},
// Required.
getLength: function () {
if (this.virtualIndex > this.numLoaded_) {
return this.numLoaded_ ;
}else{
return this.numLoaded_ + 5 ;
}
},
fetchMoreItems_ : function (index) {
if (this.toLoad_ < index) {
self.loading = true;
this.toLoad_ += 20;
self.siAsset.getAssets(this.pageNum++,20)
.then(angular.bind(this, function (assets) {
//this.objLength = assets.length;
if(! assets.statusCode){
this.items = this.items.concat(assets);
this.toLoad_ = this.items.length;
this.numLoaded_ = this.toLoad_;
}
self.loading = false;
}))
}
}
};
console.log('++++++++++',self.infiniteAssets)
<md-virtual-repeat-container id="vertical-container" ng-show="$ctrl.infiniteAssets.getLength() > 0 && $ctrl.switch">
<md-list>
<md-list-item class="list-page" md-on-demand md-virtual-repeat="asset in $ctrl.infiniteAssets | filter: {disabled: true } track by asset.id" ng-click="$ctrl.loadDetail(asset)">
<span class="search-status" style="border-left-color:{{asset.statusColor}};"></span>
<p >{{asset.name}} </p>
<label hide-xs ng-if="asset.disabled" class="ng-animate-disabled">
<md-chips >
<md-chip >{{'LABELS.DISABLED' | translate}}</md-chip>
</md-chips>
</label>
<label ><i>{{asset.status || 'UNKNOWN'}}</i></label>
<md-button aria-label="Delete Asset" class="md-icon-button md-warn" layout-padding ng-click="$ctrl.deleteAsset(asset)">
<md-icon md-svg-icon="delete" class="modelTrashIcon"></md-icon>
</md-button>
<md-divider></md-divider>
</md-list-item>
</md-list>
</md-virtual-repeat-container>
Are you certain md-virtual-repeat works with filters? AngularJS Materials virtual repeat is a custom implementation of ng-repeat, so you can't expect it to work exactly as the original. Here's from the documentation.
Virtual repeat is a limited substitute for ng-repeat that renders only
enough DOM nodes to fill the container and recycling them as the user
scrolls.
Arrays, but not objects are supported for iteration. Track by, as
alias, and (key, value) syntax are not supported.
I would move the filtering inside your controller instead and just make sure the filter is reapplied whenever the collection changes.
As per you said, "Angular 1.5 filter with ng-repeat not working by track by id"
I have created sample example using AngularJs 1.5, and used filter with track by on ng-repeat.
angular.module('controllerAsExample', [])
.controller('SettingsController1', SettingsController1);
function SettingsController1() {
this.infiniteAssets = [
{disabled :false, name:'test0',id:234 },
{disabled :true, name:'test1',id:123 },
{disabled :false, name:'test2',id:345 }
];
//console.log(this.infiniteAssets);
}
<!doctype html>
<html >
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0/angular.min.js"></script>
</head>
<body ng-app="controllerAsExample">
<div ng-controller="SettingsController1 as settings">
<p>ng-repeat with track by field example using angularjs 1.5.0:</p>
<ul>
<li ng-repeat="asset in settings.infiniteAssets | filter: {disabled: false } track by asset.id">
{{asset.name}}
</li>
</ul>
</div>
</body>
</html>

Use angularjs nested ng-repeat to construct complex table

I'm having trouble making proper table with nested ng-repeat.
What I wanted is this https://jsbin.com/razamagabo/1/edit?output
but I'm stuck at here https://plnkr.co/edit/d5voXIpzYL81sSl9BSY2?p=preview
I don't mind my markup is not table but I'm still stuck with div
<div class="row">
<div class="col-xs-6" ng-repeat="obj in data">
{{obj.date}}
<div ng-repeat="user in obj.users">
<br>
{{user.name}}
<br>
{{user.mark}}
</div>
</div>
</div>
In order for you to be able to display your data in the desired way, it will probably be easiest if you restructure your data in the JS before trying to render it.
It will be very complicated to try and match on the user names when they are in separate objects in the data array.
I would suggest processing your scope.data in the controller. (I'm assuming that you don't have much control on how you are receiving the data).
For example after you get your data...
$scope.data = [
{
date:'1-1-2016',
users:[
{
'name':'james',
'mark':18
},
{
'name':'alice',
'mark':20
}
]
},
{
date:'2-1-2016',
users:[
{
'name':'james',
'mark':60
},
{
'name':'alice',
'mark':55
}
]
}
]
var userData = {};
var possibleDates = [];
for (dataObj of Object.entries($scope.data)) {
for (userObj of dataObj) {
if ( !userData[userObj.name] ) {
userData[userObj.name] = {};
}
userData[userObj.name][dataObj.date] = userObj.mark;
if (dates.indexOf(dataObj.date) < 0) {
dates.push(dataObj.date);
}
}
}
$scope.users = userData;
$scope.dates = possibleDates;
this will give you an object like this on your scope
$scope.users = {
'james': {
'1-1-2016': 18,
'2-1-2016': 60
},
'alice': {
'1-1-2016': 20,
'2-1-2016': 55
}
};
$scope.dates = ['1-1-2016', '2-1-2016'];
This to me seems easier to structure for your template. Though this assumes each user has an entry for each date.
<div>
<div id='header-row'>
<div id='empty-corner></div>
<div class='date-header' ng-repeat='date in $scope.dates></div>
</div>
<div class='table-row' ng-repeat='{key, value} in $scope.users'>
<div class='user-name'>{{ key }}</div>
<div class='user-data' ng-repeat='date in $scope.dates>
{{ value[date] }}
</div>
</div>
</div>
As long as you apply inline-block styles to the rows/elements this should give you what you are looking for.
Though you can also think of ways to simplify your data even further. You could instead of having each user have an object where the dates are keys, you could just push the values into an array.
With your current data structure it is not possible to display it like you want. You are trying to loop over date-users objects in data array but then you want to display user from inside users array in separate rows. With ng-repeat you can loop through rows tr but not through columns. First you would need to map your data array to group elements that are supposed to be visible in 1 row into 1 object in array. Currently you have them in 2 separate objects:
James mark: 18 and James mark: 60.

Vue.js: How to map a list of keys to Firebase objects?

I develop a small web-app based on Vue.js using Firebase to store and sync the data. I store items (e.g. with attributes title and subtitle) and lists with an attribute listitems, where an array of keys (those generated from Firebase) of items is stored. The structure looks like this:
Now the problem: I want to display a list and show the items from the listitems attribute and I'm doing it like this:
Compontent:
var ShowList = Vue.extend({
template: '#show-list',
firebase: {
// get all existing items from firebase
items: firebase.database().ref('items')
},
data: function () {
// get list item keys of list 'list_id' and bind it to this.list
this.$bindAsObject('list', listsRef.child(this.$route.params.list_id));
return {
list: this.list
};
}
});
Template:
<!-- show a list -->
<template id="show-list">
<ul v-if="list.items != ''">
<li v-for="key in list.items"> <!-- I would like to not being forced to -->
<template v-for="item in items"> <!-- iterate the whole list of existing items -->
<span v-if="item['.key'] == key">
{{ item.title }}
</span>
</template>
</li>
</ul>
<div v-else>No items.</div>
</template>
As you can see, I have to use two iterations where I iterate the full items list for every entry in list.items.
My question: Is there a more efficient way to map the actual objects to the list of object keys? For a huge number of item records, my approach will be very slow. Maybe I'm just too blind to see a simpler solution?
Thanks for your time!
I think you have to denormalize/duplicate some data there. I had a similar situation and this Firebase video cleared a lot of things up for me: https://youtu.be/ran_Ylug7AE?t=2m22s (Link updated to passage at 2:22. The whole serie is worth watching btw.)
My shot at it would be adding (Firebase) keys in "listitems", just like you have done in "items", with only the most crucial data there, so that you can link to a full description
Is your data read only? In which case you could move the filter logic from your template to your data module, like so (I expect I have unintended side-effects):
data: function () {
// get list item keys of list 'list_id' and bind it to this.list
this.$bindAsObject('list', listsRef.child(this.$route.params.list_id));
var items = firebase.database().ref('items')
var activeItems = this.list.items.map(function(key) {
return items[key]
})
return {
activeItems: activeItems;
};
}

Filter variable not updating in controller angularjs

Hi Im attempting to build functionality around the length of a filter in angularjs, and although its working as it should in the view, in the controller the variable seems to stay outdated...
When I click on the div below it filters a list and calls the filterby function. The output of the length of the newly filtered list updates in the view correctly. However in the function itself I have a log set and it is still showing the old length when I click on the div.
<div ng-repeat="filter in filters" ng-click="filterby(filter.filter_type)">{{filter.filter_type}}</div>
<ul>
<li ng-repeat="event in filtered = (events | filter:query) | orderBy:'-event_date' ">
<span >{{event.event_date}},{{event.event_name}}, {{event.event_venue}}, {{event.event_description}} </span>
</li>
</ul>
<br />Length of filtered items {{filtered.length}}
And my view....
$scope.filterby = function(filterby) {
if (filterby == 'ALL') {
$scope.query = '';
}
else {
$scope.query = filterby;
}
console.log($scope.filtered.length);
};
My filter data:
$scope.filters = [
{'filter_type' : 'ALL'},
{'filter_type' : 'Macnass'}
];
EDIT: Ok its not that it nots working at all, its just showing the previous value, as if its one click behind all the time, so its something to do with the fact that the variable in the view is updated after the list is made. but Im not sure how to go about insuring the variable in the controller is the latest value.
Plunker: http://plnkr.co/edit/u7KpqYx8gDwaaXEvGeMn?p=preview
check out the plunker
added below
$scope.filtered = $filter('filter')($scope.events, $scope.query)
in $scope.filterby function

Categories

Resources