Angular .extend changing object reference - angularjs

I have an array of objects:
var items = [{id:1,name:'one'},{id:2,name:'two'}];
I then select one and make a copy:
var item = items[0];
var copy = angular.copy(item);
I then change a property:
item.name = 'changed';
What are the values?
console.log(item.name); // changed
console.log(items[0].name); // changed
The array element is the same object reference as the item, so the properties are the same.
Now I want to undo the change from the copy:
item = angular.extend(copy);
What are the values?
console.log(item.name); // one
console.log(items[0].name); // changed
By using .extend, I thought I was setting the properties on the item, and NOT changing the object reference, so I was expecting the array item to still be the same object reference as the item and thus to have the same name property, i.e. 'one'.
What went wrong?

If you have a look at angular.extend, it takes two args, dst and src. It will copy src object to dst, right? So, in this case, instead of doing following thing,
item = angular.extend(copy);
What you should be doing is,
item = angular.extend(items, copy)[0];
Here's code snippet:
var app = angular.module('myapp', []);
app.controller('MainCtrl', function($scope) {
var items = [{
id: 1,
name: 'one'
}, {
id: 2,
name: 'two'
}];
var item = items[0];
var copy = angular.copy(item);
item.name = 'changed';
console.log(item.name); // changed
console.log(items[0].name); // changed
console.log("===================");
item = angular.extend(items, copy)[0];
console.log(item.name); // one? (nope!)
console.log(items[0].name); // changed
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myapp" ng-controller="MainCtrl">
</div>

I think what I'm needing is:
Object.assign(item, copy);

Related

ngRepeat with custom index coming up blank

I'm using a large amount of arrays in a large form in my application. In order to make splicing out specific data from my datasets based on the user's selections, I've structured my arrays like this example:
var userList = [];
userList[user1.id] = user1;
userList[user2.id] = user2;
This lets me splice out specific elements without looping through the entire collection by using:
userList.splice(user1.id, 1);
However, when I try to make a list of Users in my HTML using an ng-repeat statement, it comes up blank. My HTML is:
<div data-ng-repeat="user in userList">{{user.name}}</div>
I suspect that ngRepeat uses 0,1,2.. by default and doesn't know how to handle my custom indexes. I've checked several sources but I can't really make sense of things. It did work when I added my users by simply pushing them into the array instead of assigning them to a specific index, so I know the rest of my code works fine.
Help? D:
EDIT:
The addition of a trackBy "track by user.id" didn't fix it.
Plunkr! http://plnkr.co/edit/8hANBvXAIplHsq0Ph6GX?p=preview
Your code isn't working because Array's indexes are zero-based meaning, they go from 0, 1, 2, ... n and you're trying to put alphanumeric indexes if you check the below code snippet the length of the array is zero.
var user1 = {
id: 'A1B2',
name: 'Pete'
};
var user2 = {
id: 'A2B3',
name: 'Jeff'
};
var userList = [];
userList[user1.id] = user1;
userList[user2.id] = user2;
console.log(userList);
console.log('length: ' + userList.length);
console.log(userList['A1B2']);
console.log(userList.A1B2); // behaving as JavaScript Object not array as property set using userList[user2.id] = user2;
So you need to set the data structure properly, you can set it as follows specifying the index of the array or by using the push function on the array to add a new item to the array.
var user1 = {
id: 'A1B2',
name: 'Pete'
};
var user2 = {
id: 'A2B3',
name: 'Jeff'
};
$scope.userList = [];
$scope.userList[0] = user1; // $scope.userList.push(user1);
$scope.userList[1] = user2; // $scope.userList.push(user2);
I suggest you change the collection name from userList to users it looks clean, you don't need to suffix a collection with the List keyword I think it looks untidy, just make the name plural.
angular
.module('demo', [])
.controller('DefaultController', DefaultController);
function DefaultController() {
var vm = this;
var pete = {
id: 'A1B2',
name: 'Pete'
};
var jeff = {
id: 'A2B3',
name: 'Jeff'
};
vm.users = [];
vm.users[0] = pete;
vm.users[1] = jeff;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="demo">
<div ng-controller="DefaultController as ctrl">
<div ng-repeat="user in ctrl.users">
<span>{{user.id}}, {{user.name}}</span>
</div>
</div>
</div>
Do following
Controller:
$scope.userList = [];
$scope.userList.push(user1);
$scope.userList.push(user2);
View:
<tr ng-repeat="user in userList track by $index">
<td>{{user.id}}</td>
<td>{{user.name}}</td></tr>
change the id of each individual user to numeric from alpha-numeric as pointed by Adbul, array's indexes are number based
for eg:-
var user1 = {
id: 0, //Any numeric Value
name: 'Pete'
}
and then try $scope.userList[user1.id] = user1;
<div data-ng-repeat="user in userList">{{user.name}}</div>

Angular ng-repeat, nested repeat for each parent loop

I have some trouble with getting proper data. I have many-to-many relation model, so it's 3 tables, two with data, and third is connection between them (by ID's). For example, first table is stores, second is items, and third is 'have' that connects them by id.
Now i should display available items per store. I'm using ng-repeat="store in stores" to loop through stores, and trying to create function that will return me items available in each store (store.idStore).
I have tried several approaches and none of them seems to be working for me, and since I'm new to angular, Im a little bit lost. I would appreciate any help.
Last function that I used is:
function forEachStore(id) {
angular.forEach($scope.Have, function (value, index) {
if (idStore == id) {
alert(idPlayliste);
this.push(dataFact.catchData(urlItem, idItem));
}
}, $scope.storeHasItem)
}
$scope.Have --> contains json object like({"id":1, "idStore":1, "idItem":1}, {"id":2, "idStore":1, "idItem":2}, ...)
dataFact.catchData--> my factory that gets api url and idItem and returns json object(this is working correctly).
id == store.idStore sent from ng-repeat.
So, I'm sending to this function 'store.idStore', and I want it to return me all items that is available in that store.
No alert for me :)
From your code it looks like $scope.stores is an array of Store objects. I'm assuming you have something similar for items. In your first ng-repeat loop, add something like ng-repeat="item in getItemsForStore(store.idStore)" to start the inner loop, and add the following functions to your scope (not yet tested):
$scope.getItemsForStore = function(storeId) {
var items = [];
for (var have = $scope.have, i = 0, len = have.length; i < len; i++) {
var link = have[i];
if (link.idStore === storeId && !items.contains(link.idItem) {
items.push(link.idItem);
}
}
for (int j = 0, len = items.length; j < len; j++) {
items[j] = getItem(items[j]);
}
return items;
}
function getItem(itemId) {
for (var i = 0, items = $scope.items, len = items.length; i < len; i++) {
var item = items[i];
if (item.idItem === itemId) {
return item;
}
}
throw "unknown item ID: " + itemId;
}
Things could be far more efficient if you used objects instead of arrays, with items/stores keyed by ID.
function Ctrl($scope){
$scope.stores =[
{id:1,name:'store 1'},
{id:2,name:'store 2'},
{id:3,name:'store 3'},
{id:4,name:'store 4'}
];
$scope.items =[
{id:1, name:'item 1'},
{id:2, name:'item 2'},
{id:3, name:'item 3'},
{id:4, name:'item 4'},
{id:5, name:'item 5'},
{id:6, name:'item 6'},
];
var storeItemLinked= [
{sId:1,iId:1},
{sId:1,iId:2},
{sId:1,iId:3},
{sId:2,iId:4},
{sId:2,iId:5},
{sId:2,iId:6},
{sId:3,iId:1},
{sId:3,iId:3},
{sId:3,iId:5},
{sId:4,iId:2},
{sId:4,iId:4},
{sId:4,iId:5}
];
$scope.selectedStoreItems=[];
$scope.showStoreItems=function(store){
$scope.selectedStoreItems=[];
$scope.selectedStore=store;
var itemIds=[];
for(var i=0;i<storeItemLinked.length;i++){
if(storeItemLinked[i].sId==store.id)
itemIds.push(storeItemLinked[i].iId);
}
for(var j=0;j<$scope.items.length;j++){
if(itemIds.indexOf($scope.items[j].id)>=0)
$scope.selectedStoreItems.push($scope.items[j]);
}
}
$scope.showAvailableStores = function(item){
$scope.selectedItem=item;
$scope.selectedItemStores=[];
var storeIds=[];
for(var i=0;i<storeItemLinked.length;i++){
if(storeItemLinked[i].iId==item.id)
storeIds.push(storeItemLinked[i].sId);
}
for(var j=0;j<$scope.stores.length;j++){
if(storeIds.indexOf($scope.stores[j].id)>=0)
$scope.selectedItemStores.push($scope.stores[j]);
}
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.11/angular.min.js"></script>
<div ng-app ng-controller="Ctrl">
<ul>
<li ng-repeat="store in stores" ng-click="showStoreItems(store)">{{store.name}}
<ul ng-show="selectedStore==store">
<li ng-repeat="item in selectedStoreItems" ng-click="showAvailableStores(item)">
{{item.name}}
<ul ng-show="selectedItem==item">
<li ng-repeat="store in selectedItemStores">{{store.name}}</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
click the store will list all available items then click an item will list all stores that sell that item
I just mocked up the database by some js objects and you may want to change those ng-click functions to get remote data and the logic inside will depends on the view model return from server
Edit:
On a comment inspired second read :).
I thinkidStore should be value.idStore(here:
angular.forEach($scope.Have, function (value, index) {
if (idStore == id) { ... })};
)
And also:
angular.forEach(obj, iterator, [context]), where context will provide 'this' for the iterator. You're setting context to $scope.storeHasItem - a function on the $scope, I guess it's not what you're after.
Old answer:
When you're using ng-repeat="store in stores" - you get something like this: store = stores[0] in the scope + a dom template in the view (a copy of the element on which you've put the repeat)...then store = stores[1] and a copy...and so on
So in the function, the parameter you're getting is a whole store object, not just the id. Try a console.log - it should clarify things.

Updating selected array element after deleting the element from a filtered ng-repeat

I have a list of users where you click on a user to select it and put the element into an object $scope.selectedMember when selected. I'm using ng-repeat with a search box filter. The important thing is that $scope.selectedMember should always be populated with a member.
Problem is that i'm trying to overcome:
- splicing the last user out needs to automatically select the last user in the filtered array, even if it's filtered some members out with the search.
HTML
<div ng-app="myApp" ng-controller="MainCtrl">
<input ng-model="search"></input>
<div ng-repeat="(key, member) in members | filter:search | orderBy :'-name'">
<li ng-class="{active: retActive(key)}"
ng-click="selectThis($index)">
name: {{member.name}} key: {{key}}
<button ng-click="deleteThis(key)">delete</button>
</li>
</div>
Selected member name: {{selectedMember.name}}
</div>
JS
angular.module('myApp', []);
function MainCtrl($scope) {
$scope.members = [
{"name":"a", "viewIndex":0},
{"name":"b", "viewIndex":1},
{"name":"bb", "viewIndex":2},
{"name":"bb", "viewIndex":3},
{"name":"c", "viewIndex":4},
{"name":"d", "viewIndex":5},
{"name":"e", "viewIndex":6}
];
$scope.activeIndex = 0;
$scope.selectedMember = $scope.members[0];
$scope.selectThis = function (index) {
$scope.activeIndex = index;
//put array member into new object
$scope.selectedMember = $scope.members[index];
}
//splice the selected member from the array
$scope.deleteThis = function (index) {
$scope.members.splice(index, 1);
$scope.selectThis(index);
}
//angular copy and push member to the array
$scope.duplicateThis = function (index) {
}
// change class if member is active
$scope.retActive = function (index) {
return $scope.activeIndex == index;
}
}
CSS
.active {
color:blue;
}
Link to JSFiddle
One problem I see is that you are passing $index to selectThis($index). When you filter data, the loop's $index no longer represents the actual index of an item in the array - so you should pass selectThis a key, not $index.

AngularJS - Changing a variable inside a controller method doesn't update the $scope variable

I want to create a method in my controller to set to null different variables from my controller $scope. So I've this in my controller :
FootCreator.controller('FooController', function($scope) {
$scope.zoo = {
id: 1,
name: 'Lorem',
};
$scope.foo = {
id: 2,
title: 'bar',
};
$scope.deleteProperty = function(property) {
property = null;
};
});
And in my HTML I call it this way (for example) :
<a ng-click="deleteProperty(zoo)" class="remove icon-remove" title="Remove"></a>
When I console.log() the $scope.zoo it's not set to null. I think I must do something bad but can't find what. I try to do that not to have a deleteZoo(), deleteFoo() etc.
Thanks for the help/tips !
In your deleteProperty method, you are just setting the property parameter to null and this is never going to have any effect on your scope.
A simplified example of what you're doing here is:
$scope.zoo = { id: 1, name: 'Lorem' };
var property = $scope.zoo;
property = null;
console.log($scope.zoo); // previous line had no effect on $scope.zoo
I would suggest passing the property name as a string instead of the property itself. Then you can do this:
$scope.deleteProperty = function(property) {
delete $scope[property];
};
<a ng-click="deleteProperty('zoo')" class="remove icon-remove"
title="Remove"></a>
If you really want to pass the property itself (like you're doing in your HTML), you'd need to loop through all the properties to find the one that matches:
$scope.deleteProperty = function(property) {
for (var p in $scope) {
if ($scope.hasOwnProperty(p) && $scope[p] === property) {
delete $scope[p];
}
}
};

<select> doesn't have a selected value

I try to set the value of my select dynamically in the controller the following way:
angular.module('vocabApp.controllers', []).
controller('indexController', function ($scope, dictionaryHandler) {
$scope.expressions = [
{ expression: "", meanings: ["first", "second"], selected: "" }];
$scope.counter = 0;
$scope.add = function (expressionString, expressionObject) {
if (expressionString.length == 1) {
$scope.expressions.push({ expression: "", meanings: [""] });
}
dictionaryHandler.sendJsonpRequestToGetMeanings(expressionString, function (meaningsWeAcquired) {
alert("callback called!");
expressionObject.meanings = meaningsWeAcquired;
expressionObject.selected = expressionObject.meanings[0];
});
};
});
The odd thing is that the content of "meaningsWeAcquired" shows up in the select list, but there is no default selected value. Here's the html:
<select ng-init="option.selected = item.meanings[0]" ng-model="option.selected" ng-options="option as option for option in item.meanings"></select>
On page load the default value is "first", so I don't get why the same thing doesn't work when I set the object's "selected" field from the callback method.
I can see that you are setting expressionObject.selected in the sendJsonpRequestToGetMeanings callback, but in the HTML the select ng-model attribute is set to "option.selected", which does not point to the value you set before. I guess that the select element is wrapped by the ng-repeat directive and "item" references current expresssionObject.
So you should try changing:
ng-model="option.selected"
to
ng-model="item.selected"

Resources