user1513857 user1513857 - 1 month ago 13
AngularJS Question

Change in scope variable updating Service variable

Can somebody please have a look at below plunker?

http://plnkr.co/edit/oTHJcVVzJE5CwMnFMEpS?p=preview

Problem: Change in the scope variable "specifications.selectedColor" is overwriting the specifications of other products i.e changing the selectedColor of all the products.

Steps to reproduce:
1) Click on Buy Now. checkoutProductList[0].selectedColor is set to Red.
2) Select Blue color from the drop-down.
3) Click on Buy Now. checkoutProductList[0].selectedColor is set to Blue.

On clicking Buy Now second time(step 3 above), it adds another product in checkoutProductList Array i.e checkoutProductList[1], it should not change
checkoutProductList[0].selectedColor from Red to Blue.

What can be done to keep specifications of each product separately?

Code:
Contoller

var app = angular.module('plunker', ['ui.select', 'ngSanitize']);

app.controller('MainCtrl', function($rootScope, $scope,checkoutService) {

$scope.productAvailableColors=['Red','Blue'];
$scope.specifications={'selectedColor':'Red'};



$scope.buyNow = function (){

checkoutService.setCheckoutProductList($scope.specifications);
};

});


app.service ('checkoutService', function (){
var checkoutProductList=[];
this.setCheckoutProductList = function ( specs){
checkoutProductList.push(specs);
window.alert(checkoutProductList.length);
window.alert(checkoutProductList[0].selectedColor);
}

})


HEAD

HTML:
<!DOCTYPE html>
<html ng-app="plunker">

<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>

<script>
document.write('<base href="' + document.location + '" />');
</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js@1.4.x" src="https://code.angularjs.org/1.4.12/angular.js" data-semver="1.4.9"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.3/css/bootstrap.min.css" integrity="sha384-MIwDKRSSImVFAZCVLtU0LMDdON6KVCrZHyVQQj6e8wIEJkW4tvwqXrbMIya1vriY" crossorigin="anonymous">
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.0.0/jquery.min.js" integrity="sha384-THPy051/pYDQGanwU6poAc/hOdQxjnOEXzbT+OuUAFqNqFjL+4IGLBgCJC3ZOShY" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.2.0/js/tether.min.js" integrity="sha384-Plbmg8JY28KFelvJVai01l8WyZzrYWG825m+cZ0eDDS1f7d/js6ikvy1+X+guPIB" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.3/js/bootstrap.min.js" integrity="sha384-ux8v3A6CPtOTqOzMKiuo3d/DomGaaClxFYdCu2HPMBEkf6x2xiDyJ7gkXU0MWwaD" crossorigin="anonymous"></script>


<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.12/angular-sanitize.js"></script>
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/select2/3.4.5/select2.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.8.5/css/selectize.default.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-select/0.19.4/select.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-select/0.19.4/select.min.css">



<script src="app.js"></script>
</head>


BODY

<body ng-controller="MainCtrl">

<div class="productModal-circle"
style="cursor: pointer; font-size: 14px; "

aria-hidden="true"
ng-click="buyNow()"
title="Buy Now!" id="buyNow">Buy Now

</div>

<div id="choices" class="panel-collapse collapse in">


<div class="panel-body">
<ui-select ng-model="$parent.specifications.selectedColor"
theme="bootstrap"
ng-disabled="false"
close-on-select="true"
style="color: black;"
title="Choose Color and Design"
>
<ui-select-match placeholder="Choose Color/Design">{{$parent.specifications.selectedColor}}</ui-select-match>
<ui-select-choices repeat="listItem in productAvailableColors | filter:$select.search">
<div ng-bind-html="listItem | highlight: $select.search"></div>
</ui-select-choices>
</ui-select>
</div>

</div>
{{specifications | json}}
</body>


</html>

Answer

Problem

The problem lies in deep copy and shallow copy in JavaScript. When you are pushing an object to the array, it's not really creating a copy of the object and pushing it instead it just stores a reference to the original object (which is $scope.specifications in your case). Since you are changing the $scope.specifications object in your controller your array is also getting changed.

Solution

You can leverage the angular.copy method which is used to create deep copy of an object. So what you can do is before pushing the object to the array, you can make a deep copy of the object and then push it to the array.

In your service where you are pushing the object to the array make this change

checkoutProductList.push(angular.copy(specs));

Full Service

app.service ('checkoutService', function (){
    var checkoutProductList=[];
    this.setCheckoutProductList = function ( specs){
    checkoutProductList.push(angular.copy(specs));
    window.alert(checkoutProductList.length);
    window.alert(JSON.stringify(checkoutProductList));
 }

I have also created a plunkr to demonstrate the working solution. You can see it here.